exchange

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

commit 54fc83ee6b910d482948c6ec8185df7aab1b0cb1
parent 57ab9f9fdba607fcc3817adf58f37c5390f8d220
Author: Christian Grothoff <christian@grothoff.org>
Date:   Fri, 11 Jan 2019 21:27:34 +0100

fix cyclic dependency by combining exchange-lib and auditor-lib directories

Diffstat:
M.gitignore | 22+++++++++++-----------
Mconfigure.ac | 3+--
Msrc/Makefile.am | 6+++---
Dsrc/auditor-lib/Makefile.am | 90-------------------------------------------------------------------------------
Dsrc/auditor-lib/auditor_api_deposit_confirmation.c | 384-------------------------------------------------------------------------------
Dsrc/auditor-lib/auditor_api_exchanges.c | 245-------------------------------------------------------------------------------
Dsrc/auditor-lib/auditor_api_handle.c | 527-------------------------------------------------------------------------------
Dsrc/auditor-lib/backoff.h | 38--------------------------------------
Dsrc/auditor-lib/curl_defaults.c | 75---------------------------------------------------------------------------
Dsrc/auditor-lib/test_exchange_api_home/.local/share/taler/auditor/offline-keys/auditor.priv | 2--
Dsrc/auditor-lib/test_exchange_api_home/.local/share/taler/exchange/wirefees/x-taler-bank.fee | 0
Dsrc/auditor-lib/testing_auditor_api_cmd_deposit_confirmation.c | 411-------------------------------------------------------------------------------
Dsrc/auditor-lib/testing_auditor_api_cmd_exchanges.c | 296-------------------------------------------------------------------------------
Msrc/benchmark/Makefile.am | 4++--
Dsrc/exchange-lib/Makefile.am | 181-------------------------------------------------------------------------------
Dsrc/exchange-lib/curl_defaults.c | 75---------------------------------------------------------------------------
Dsrc/exchange-lib/curl_defaults.h | 41-----------------------------------------
Dsrc/exchange-lib/exchange_api_deposit.c | 597-------------------------------------------------------------------------------
Dsrc/exchange-lib/exchange_api_handle.c | 1779-------------------------------------------------------------------------------
Dsrc/exchange-lib/exchange_api_payback.c | 374-------------------------------------------------------------------------------
Dsrc/exchange-lib/exchange_api_refresh.c | 1678-------------------------------------------------------------------------------
Dsrc/exchange-lib/exchange_api_refresh_link.c | 444-------------------------------------------------------------------------------
Dsrc/exchange-lib/exchange_api_refund.c | 416-------------------------------------------------------------------------------
Dsrc/exchange-lib/exchange_api_reserve.c | 1203-------------------------------------------------------------------------------
Dsrc/exchange-lib/exchange_api_track_transaction.c | 367-------------------------------------------------------------------------------
Dsrc/exchange-lib/exchange_api_track_transfer.c | 388-------------------------------------------------------------------------------
Dsrc/exchange-lib/exchange_api_wire.c | 442-------------------------------------------------------------------------------
Dsrc/exchange-lib/test_exchange_api_home/.config/taler/account-1.json | 6------
Dsrc/exchange-lib/test_exchange_api_home/.config/taler/account-2.json | 5-----
Dsrc/exchange-lib/test_exchange_api_home/.config/taler/sepa.json | 10----------
Dsrc/exchange-lib/test_exchange_api_home/.config/taler/test.json | 8--------
Dsrc/exchange-lib/test_exchange_api_home/.config/taler/x-taler-bank.json | 5-----
Dsrc/exchange-lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv | 2--
Asrc/lib/Makefile.am | 255+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/exchange-lib/afl-generate.sh -> src/lib/afl-generate.sh | 0
Asrc/lib/auditor_api_curl_defaults.c | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/auditor-lib/curl_defaults.h -> src/lib/auditor_api_curl_defaults.h | 0
Asrc/lib/auditor_api_deposit_confirmation.c | 384+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/auditor_api_exchanges.c | 245+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/auditor_api_handle.c | 527+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/auditor-lib/auditor_api_handle.h -> src/lib/auditor_api_handle.h | 0
Rsrc/exchange-lib/backoff.h -> src/lib/backoff.h | 0
Rsrc/exchange-lib/baseline/admin_add_incoming.req -> src/lib/baseline/admin_add_incoming.req | 0
Rsrc/exchange-lib/baseline/deposit.req -> src/lib/baseline/deposit.req | 0
Rsrc/exchange-lib/baseline/keys.req -> src/lib/baseline/keys.req | 0
Rsrc/exchange-lib/baseline/refresh_link.req -> src/lib/baseline/refresh_link.req | 0
Rsrc/exchange-lib/baseline/refresh_melt.req -> src/lib/baseline/refresh_melt.req | 0
Rsrc/exchange-lib/baseline/refresh_reveal.req -> src/lib/baseline/refresh_reveal.req | 0
Rsrc/exchange-lib/baseline/reserve_status.req -> src/lib/baseline/reserve_status.req | 0
Rsrc/exchange-lib/baseline/reserve_withdraw.req -> src/lib/baseline/reserve_withdraw.req | 0
Rsrc/exchange-lib/baseline/wire.req -> src/lib/baseline/wire.req | 0
Rsrc/exchange-lib/baseline/wire_sepa.req -> src/lib/baseline/wire_sepa.req | 0
Rsrc/exchange-lib/baseline/wire_test.req -> src/lib/baseline/wire_test.req | 0
Rsrc/exchange-lib/exchange_api_common.c -> src/lib/exchange_api_common.c | 0
Asrc/lib/exchange_api_curl_defaults.c | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_curl_defaults.h | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_deposit.c | 597+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_handle.c | 1779+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/exchange-lib/exchange_api_handle.h -> src/lib/exchange_api_handle.h | 0
Asrc/lib/exchange_api_payback.c | 374+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_refresh.c | 1678+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_refresh_link.c | 444+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_refund.c | 416+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_reserve.c | 1203+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_track_transaction.c | 367+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_track_transfer.c | 388+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_wire.c | 442+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/auditor-lib/test_auditor_api.c -> src/lib/test_auditor_api.c | 0
Rsrc/auditor-lib/test_auditor_api.conf -> src/lib/test_auditor_api.conf | 0
Rsrc/auditor-lib/test_auditor_api_expire_reserve_now.conf -> src/lib/test_auditor_api_expire_reserve_now.conf | 0
Rsrc/exchange-lib/test_exchange_api.conf -> src/lib/test_exchange_api.conf | 0
Rsrc/exchange-lib/test_exchange_api_expire_reserve_now.conf -> src/lib/test_exchange_api_expire_reserve_now.conf | 0
Rsrc/auditor-lib/test_exchange_api_home/.config/taler/account-1.json -> src/lib/test_exchange_api_home/.config/taler/account-1.json | 0
Rsrc/auditor-lib/test_exchange_api_home/.config/taler/account-2.json -> src/lib/test_exchange_api_home/.config/taler/account-2.json | 0
Rsrc/auditor-lib/test_exchange_api_home/.config/taler/sepa.json -> src/lib/test_exchange_api_home/.config/taler/sepa.json | 0
Rsrc/auditor-lib/test_exchange_api_home/.config/taler/test.json -> src/lib/test_exchange_api_home/.config/taler/test.json | 0
Rsrc/auditor-lib/test_exchange_api_home/.config/taler/x-taler-bank.json -> src/lib/test_exchange_api_home/.config/taler/x-taler-bank.json | 0
Rsrc/auditor-lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv -> src/lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv | 0
Rsrc/exchange-lib/test_exchange_api_keys_cherry_picking.conf -> src/lib/test_exchange_api_keys_cherry_picking.conf | 0
Rsrc/exchange-lib/test_exchange_api_keys_cherry_picking_extended.conf -> src/lib/test_exchange_api_keys_cherry_picking_extended.conf | 0
Rsrc/exchange-lib/test_exchange_api_keys_cherry_picking_extended_2.conf -> src/lib/test_exchange_api_keys_cherry_picking_extended_2.conf | 0
Rsrc/exchange-lib/test_exchange_api_keys_cherry_picking_new.c -> src/lib/test_exchange_api_keys_cherry_picking_new.c | 0
Rsrc/exchange-lib/test_exchange_api_new.c -> src/lib/test_exchange_api_new.c | 0
Rsrc/exchange-lib/test_exchange_api_overlapping_keys_bug.c -> src/lib/test_exchange_api_overlapping_keys_bug.c | 0
Rsrc/exchange-lib/test_exchange_api_twisted.c -> src/lib/test_exchange_api_twisted.c | 0
Rsrc/exchange-lib/test_exchange_api_twisted.conf -> src/lib/test_exchange_api_twisted.conf | 0
Rsrc/exchange-lib/testing_api_cmd_bank_check.c -> src/lib/testing_api_cmd_bank_check.c | 0
Rsrc/exchange-lib/testing_api_cmd_batch.c -> src/lib/testing_api_cmd_batch.c | 0
Rsrc/exchange-lib/testing_api_cmd_check_keys.c -> src/lib/testing_api_cmd_check_keys.c | 0
Rsrc/exchange-lib/testing_api_cmd_deposit.c -> src/lib/testing_api_cmd_deposit.c | 0
Rsrc/exchange-lib/testing_api_cmd_exec_aggregator.c -> src/lib/testing_api_cmd_exec_aggregator.c | 0
Rsrc/exchange-lib/testing_api_cmd_exec_auditor-sign.c -> src/lib/testing_api_cmd_exec_auditor-sign.c | 0
Rsrc/exchange-lib/testing_api_cmd_exec_keyup.c -> src/lib/testing_api_cmd_exec_keyup.c | 0
Rsrc/exchange-lib/testing_api_cmd_exec_wirewatch.c -> src/lib/testing_api_cmd_exec_wirewatch.c | 0
Rsrc/exchange-lib/testing_api_cmd_fakebank_transfer.c -> src/lib/testing_api_cmd_fakebank_transfer.c | 0
Rsrc/exchange-lib/testing_api_cmd_payback.c -> src/lib/testing_api_cmd_payback.c | 0
Rsrc/exchange-lib/testing_api_cmd_refresh.c -> src/lib/testing_api_cmd_refresh.c | 0
Rsrc/exchange-lib/testing_api_cmd_refund.c -> src/lib/testing_api_cmd_refund.c | 0
Rsrc/exchange-lib/testing_api_cmd_serialize_keys.c -> src/lib/testing_api_cmd_serialize_keys.c | 0
Rsrc/exchange-lib/testing_api_cmd_signal.c -> src/lib/testing_api_cmd_signal.c | 0
Rsrc/exchange-lib/testing_api_cmd_sleep.c -> src/lib/testing_api_cmd_sleep.c | 0
Rsrc/exchange-lib/testing_api_cmd_status.c -> src/lib/testing_api_cmd_status.c | 0
Rsrc/exchange-lib/testing_api_cmd_track.c -> src/lib/testing_api_cmd_track.c | 0
Rsrc/exchange-lib/testing_api_cmd_wire.c -> src/lib/testing_api_cmd_wire.c | 0
Rsrc/exchange-lib/testing_api_cmd_withdraw.c -> src/lib/testing_api_cmd_withdraw.c | 0
Rsrc/exchange-lib/testing_api_helpers.c -> src/lib/testing_api_helpers.c | 0
Rsrc/exchange-lib/testing_api_loop.c -> src/lib/testing_api_loop.c | 0
Rsrc/exchange-lib/testing_api_trait_amount.c -> src/lib/testing_api_trait_amount.c | 0
Rsrc/exchange-lib/testing_api_trait_blinding_key.c -> src/lib/testing_api_trait_blinding_key.c | 0
Rsrc/exchange-lib/testing_api_trait_cmd.c -> src/lib/testing_api_trait_cmd.c | 0
Rsrc/exchange-lib/testing_api_trait_coin_priv.c -> src/lib/testing_api_trait_coin_priv.c | 0
Rsrc/exchange-lib/testing_api_trait_denom_pub.c -> src/lib/testing_api_trait_denom_pub.c | 0
Rsrc/exchange-lib/testing_api_trait_denom_sig.c -> src/lib/testing_api_trait_denom_sig.c | 0
Rsrc/exchange-lib/testing_api_trait_exchange_pub.c -> src/lib/testing_api_trait_exchange_pub.c | 0
Rsrc/exchange-lib/testing_api_trait_exchange_sig.c -> src/lib/testing_api_trait_exchange_sig.c | 0
Rsrc/exchange-lib/testing_api_trait_fresh_coin.c -> src/lib/testing_api_trait_fresh_coin.c | 0
Rsrc/exchange-lib/testing_api_trait_json.c -> src/lib/testing_api_trait_json.c | 0
Rsrc/exchange-lib/testing_api_trait_key_peer.c -> src/lib/testing_api_trait_key_peer.c | 0
Rsrc/exchange-lib/testing_api_trait_number.c -> src/lib/testing_api_trait_number.c | 0
Rsrc/exchange-lib/testing_api_trait_process.c -> src/lib/testing_api_trait_process.c | 0
Rsrc/exchange-lib/testing_api_trait_reserve_priv.c -> src/lib/testing_api_trait_reserve_priv.c | 0
Rsrc/exchange-lib/testing_api_trait_string.c -> src/lib/testing_api_trait_string.c | 0
Rsrc/exchange-lib/testing_api_trait_wtid.c -> src/lib/testing_api_trait_wtid.c | 0
Rsrc/exchange-lib/testing_api_traits.c -> src/lib/testing_api_traits.c | 0
Asrc/lib/testing_auditor_api_cmd_deposit_confirmation.c | 411+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/testing_auditor_api_cmd_exchanges.c | 296+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/auditor-lib/testing_auditor_api_cmd_exec_auditor.c -> src/lib/testing_auditor_api_cmd_exec_auditor.c | 0
Rsrc/auditor-lib/testing_auditor_api_cmd_exec_auditor_dbinit.c -> src/lib/testing_auditor_api_cmd_exec_auditor_dbinit.c | 0
Rsrc/auditor-lib/testing_auditor_api_cmd_exec_wire_auditor.c -> src/lib/testing_auditor_api_cmd_exec_wire_auditor.c | 0
Rsrc/auditor-lib/testing_auditor_api_helpers.c -> src/lib/testing_auditor_api_helpers.c | 0
130 files changed, 10014 insertions(+), 10107 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -40,14 +40,14 @@ src/bank-lib/test_bank_api_new src/bank-lib/test_bank_api_with_fakebank src/bank-lib/test_bank_api_with_fakebank_new src/bank-lib/test_bank_api_with_fakebank_twisted -src/exchange-lib/test_exchange_api_new -src/exchange-lib/test_exchange_api -src/exchange-lib/test_exchange_api_home/.local/share/taler/exchange/live-keys/ -src/exchange-lib/test_exchange_api_home/.local/share/taler/exchange/wirefees/ -src/exchange-lib/test_exchange_api_home/.local/share/taler/auditor/ -src/exchange-lib/test_exchange_api_home/.local/share/taler/auditors/ -src/exchange-lib/auditor.in -src/exchange-lib/test_exchange_api_twisted +src/lib/test_exchange_api_new +src/lib/test_exchange_api +src/lib/test_exchange_api_home/.local/share/taler/exchange/live-keys/ +src/lib/test_exchange_api_home/.local/share/taler/exchange/wirefees/ +src/lib/test_exchange_api_home/.local/share/taler/auditor/ +src/lib/test_exchange_api_home/.local/share/taler/auditors/ +src/lib/auditor.in +src/lib/test_exchange_api_twisted src/exchange/taler-exchange-aggregator src/exchange/test_taler_exchange_aggregator-postgres src/exchange/test_taler_exchange_httpd_home/.local/share/taler/exchange/live-keys/ @@ -97,8 +97,8 @@ doc/manual/manual.tp doc/manual/manual.vr contrib/taler-exchange.tag doxygen-doc/ -src/exchange-lib/test_exchange_api_keys_cherry_picking -src/exchange-lib/test_exchange_api_keys_cherry_picking_new +src/lib/test_exchange_api_keys_cherry_picking +src/lib/test_exchange_api_keys_cherry_picking_new src/auditor/taler-wire-auditor contrib/auditor-report.aux contrib/auditor-report.log @@ -106,4 +106,4 @@ contrib/auditor-report.tex contrib/auditor-report.pdf src/bank-lib/taler-bank-transfer src/bank-lib/test_bank_api_twisted -src/exchange-lib/test_exchange_api_new +src/lib/test_exchange_api_new diff --git a/configure.ac b/configure.ac @@ -546,12 +546,11 @@ AC_CONFIG_FILES([Makefile src/Makefile src/auditor/Makefile src/auditordb/Makefile - src/auditor-lib/Makefile src/bank-lib/Makefile src/exchange/Makefile src/exchangedb/Makefile src/exchange-tools/Makefile - src/exchange-lib/Makefile + src/lib/Makefile src/benchmark/Makefile src/include/Makefile src/json/Makefile diff --git a/src/Makefile.am b/src/Makefile.am @@ -22,12 +22,12 @@ pkgcfg_DATA = \ EXTRA_DIST = \ taler.conf -SUBDIRS = include util wire json $(PQ_DIR) $(BANK_LIB) wire-plugins exchangedb exchange exchange-tools auditordb auditor +SUBDIRS = include util wire json $(PQ_DIR) $(BANK_LIB) wire-plugins exchangedb exchange exchange-tools auditordb auditor if HAVE_LIBCURL - SUBDIRS += exchange-lib auditor-lib benchmark + SUBDIRS += lib benchmark else if HAVE_LIBGNURL - SUBDIRS += exchange-lib auditor-lib benchmark + SUBDIRS += lib benchmark endif endif diff --git a/src/auditor-lib/Makefile.am b/src/auditor-lib/Makefile.am @@ -1,90 +0,0 @@ -# This Makefile.am is in the public domain -AM_CPPFLAGS = -I$(top_srcdir)/src/include - -if USE_COVERAGE - AM_CFLAGS = --coverage -O0 - XLIB = -lgcov -endif - -lib_LTLIBRARIES = \ - libtalerauditor.la \ - libtalerauditortesting.la - -libtalerauditor_la_LDFLAGS = \ - -version-info 0:0:0 \ - -no-undefined -libtalerauditor_la_SOURCES = \ - curl_defaults.c \ - auditor_api_handle.c auditor_api_handle.h \ - auditor_api_deposit_confirmation.c \ - auditor_api_exchanges.c -libtalerauditor_la_LIBADD = \ - $(top_builddir)/src/json/libtalerjson.la \ - $(top_builddir)/src/util/libtalerutil.la \ - -lgnunetcurl \ - -lgnunetjson \ - -lgnunetutil \ - -ljansson \ - $(XLIB) - -if HAVE_LIBCURL -libtalerauditor_la_LIBADD += -lcurl -else -if HAVE_LIBGNURL -libtalerauditor_la_LIBADD += -lgnurl -endif -endif - - -libtalerauditortesting_la_LDFLAGS = \ - -version-info 0:0:0 \ - -no-undefined -libtalerauditortesting_la_SOURCES = \ - testing_auditor_api_helpers.c \ - testing_auditor_api_cmd_deposit_confirmation.c \ - testing_auditor_api_cmd_exchanges.c \ - testing_auditor_api_cmd_exec_auditor.c \ - testing_auditor_api_cmd_exec_auditor_dbinit.c \ - testing_auditor_api_cmd_exec_wire_auditor.c -libtalerauditortesting_la_LIBADD = \ - libtalerauditor.la \ - $(top_builddir)/src/exchange-lib/libtalerexchange.la \ - $(top_builddir)/src/exchange-lib/libtalertesting.la \ - $(top_builddir)/src/wire/libtalerwire.la \ - $(top_builddir)/src/json/libtalerjson.la \ - $(top_builddir)/src/util/libtalerutil.la \ - -lgnunetcurl \ - -lgnunetjson \ - -lgnunetutil \ - -ljansson \ - $(XLIB) - - -check_PROGRAMS = \ - test_auditor_api - -AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; - -TESTS = \ - $(check_PROGRAMS) - -test_auditor_api_SOURCES = \ - test_auditor_api.c -test_auditor_api_LDADD = \ - libtalerauditortesting.la \ - libtalerauditor.la \ - $(top_builddir)/src/exchange-lib/libtalertesting.la \ - $(top_builddir)/src/exchange-lib/libtalerexchange.la \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/bank-lib/libtalerfakebank.la \ - $(top_builddir)/src/bank-lib/libtalerbank.la \ - $(top_builddir)/src/json/libtalerjson.la \ - $(top_builddir)/src/util/libtalerutil.la \ - -lgnunetcurl \ - -lgnunetutil \ - -ljansson - - -EXTRA_DIST = \ - test_auditor_api.conf \ - test_auditor_api_expire_reserve_now.conf diff --git a/src/auditor-lib/auditor_api_deposit_confirmation.c b/src/auditor-lib/auditor_api_deposit_confirmation.c @@ -1,384 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2018 GNUnet e.V. - - 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 auditor-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_json_lib.h" -#include "taler_auditor_service.h" -#include "auditor_api_handle.h" -#include "taler_signatures.h" -#include "curl_defaults.h" - - -/** - * @brief A DepositConfirmation Handle - */ -struct TALER_AUDITOR_DepositConfirmationHandle -{ - - /** - * The connection to auditor this request handle will use - */ - struct TALER_AUDITOR_Handle *auditor; - - /** - * The url for this request. - */ - char *url; - - /** - * JSON encoding of the request to POST. - */ - char *json_enc; - - /** - * 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; - - dh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - break; - 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 */ - break; - case MHD_HTTP_UNAUTHORIZED: - /* 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: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - dh->cb (dh->cb_cls, - response_code, - TALER_JSON_get_error_code (json), - json); - TALER_AUDITOR_deposit_confirmation_cancel (dh); -} - - -/** - * Verify signature information about the deposit-confirmation. - * - * @param h_wire hash of merchant wire details - * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor) - * @param timestamp timestamp when the contract was finalized, must not be too far in the future - * @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 int -verify_signatures (const struct GNUNET_HashCode *h_wire, - const struct GNUNET_HashCode *h_contract_terms, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute refund_deadline, - const struct TALER_Amount *amount_without_fee, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - 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_Absolute ep_start, - struct GNUNET_TIME_Absolute ep_expire, - struct GNUNET_TIME_Absolute ep_end, - const struct TALER_MasterSignatureP *master_sig) -{ - struct TALER_DepositConfirmationPS dc; - struct TALER_ExchangeSigningKeyValidityPS sv; - - dc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT); - dc.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)); - dc.h_contract_terms = *h_contract_terms; - dc.h_wire = *h_wire; - dc.timestamp = GNUNET_TIME_absolute_hton (timestamp); - dc.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); - TALER_amount_hton (&dc.amount_without_fee, - amount_without_fee); - dc.coin_pub = *coin_pub; - dc.merchant = *merchant_pub; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT, - &dc.purpose, - &exchange_sig->eddsa_signature, - &exchange_pub->eddsa_pub)) - { - 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; - } - sv.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY); - sv.purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS)); - sv.master_public_key = *master_pub; - sv.start = GNUNET_TIME_absolute_hton (ep_start); - sv.expire = GNUNET_TIME_absolute_hton (ep_expire); - sv.end = GNUNET_TIME_absolute_hton (ep_end); - sv.signkey_pub = *exchange_pub; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY, - &sv.purpose, - &master_sig->eddsa_signature, - &master_pub->eddsa_pub)) - { - GNUNET_break (0); - TALER_LOG_WARNING ("Invalid signature on exchange signing key!\n"); - return GNUNET_SYSERR; - } - if (0 == GNUNET_TIME_absolute_get_remaining (ep_end).rel_value_us) - { - GNUNET_break (0); - TALER_LOG_WARNING ("Exchange signing key is no longer valid!\n"); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Submit a deposit-confirmation permission to the auditor and get the - * auditor's response. Note that while we return the response - * verbatim to the caller for further processing, we do already verify - * that the response is well-formed. If the auditor's reply is not - * well-formed, we return an HTTP status code of zero to @a cb. - * - * We also verify that the @a exchange_sig is valid for this deposit-confirmation - * request, and that the @a master_sig is a valid signature for @a - * exchange_pub. Also, the @a auditor must be ready to operate (i.e. have - * finished processing the /version reply). If either check fails, we do - * NOT initiate the transaction with the auditor and instead return NULL. - * - * @param auditor the auditor handle; the auditor must be ready to operate - * @param h_wire hash of merchant wire details - * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor) - * @param timestamp timestamp when the contract was finalized, must not be too far in the future - * @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 - * @param cb the callback to call when a reply for this request is available - * @param cb_cls closure for the above callback - * @return a handle for this request; NULL if the inputs are invalid (i.e. - * signatures fail to verify). In this case, the callback is not called. - */ -struct TALER_AUDITOR_DepositConfirmationHandle * -TALER_AUDITOR_deposit_confirmation (struct TALER_AUDITOR_Handle *auditor, - const struct GNUNET_HashCode *h_wire, - const struct GNUNET_HashCode *h_contract_terms, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute refund_deadline, - const struct TALER_Amount *amount_without_fee, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - 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_Absolute ep_start, - struct GNUNET_TIME_Absolute ep_expire, - struct GNUNET_TIME_Absolute ep_end, - const struct TALER_MasterSignatureP *master_sig, - TALER_AUDITOR_DepositConfirmationResultCallback cb, - void *cb_cls) -{ - struct TALER_AUDITOR_DepositConfirmationHandle *dh; - struct GNUNET_CURL_Context *ctx; - json_t *deposit_confirmation_obj; - CURL *eh; - - (void) GNUNET_TIME_round_abs (&timestamp); - (void) GNUNET_TIME_round_abs (&refund_deadline); - (void) GNUNET_TIME_round_abs (&ep_start); - (void) GNUNET_TIME_round_abs (&ep_expire); - (void) GNUNET_TIME_round_abs (&ep_end); - GNUNET_assert (GNUNET_YES == - MAH_handle_is_ready (auditor)); - if (GNUNET_OK != - verify_signatures (h_wire, - h_contract_terms, - timestamp, - refund_deadline, - amount_without_fee, - coin_pub, - merchant_pub, - exchange_pub, - exchange_sig, - master_pub, - ep_start, - ep_expire, - ep_end, - master_sig)) - { - GNUNET_break_op (0); - return NULL; - } - - deposit_confirmation_obj - = json_pack ("{s:o, s:o," /* H_wire, h_contract_terms */ - " s:o, s:o," /* timestamp, refund_deadline */ - " s:o, s:o," /* amount_without_fees, coin_pub */ - " s:o, s:o," /* merchant_pub, exchange_sig */ - " s:o, s:o," /* master_pub, ep_start */ - " s:o, s:o," /* ep_expire, ep_end */ - " s:o}", /* master_sig */ - "H_wire", GNUNET_JSON_from_data_auto (&h_wire), - "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms), - "timestamp", GNUNET_JSON_from_time_abs (timestamp), - "refund_deadline", GNUNET_JSON_from_time_abs (refund_deadline), - "amount_without_fee", TALER_JSON_from_amount (amount_without_fee), - "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), - "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub), - "exchange_sig", GNUNET_JSON_from_data_auto (exchange_sig), - "master_pub", GNUNET_JSON_from_data_auto (master_pub), - "ep_start", GNUNET_JSON_from_time_abs (ep_start), - "ep_expire", GNUNET_JSON_from_time_abs (ep_expire), - "ep_end", GNUNET_JSON_from_time_abs (ep_end), - "master_sig", GNUNET_JSON_from_data_auto (master_sig)); - if (NULL == deposit_confirmation_obj) - { - GNUNET_break (0); - return NULL; - } - - dh = GNUNET_new (struct TALER_AUDITOR_DepositConfirmationHandle); - dh->auditor = auditor; - dh->cb = cb; - dh->cb_cls = cb_cls; - dh->url = MAH_path_to_url (auditor, "/deposit-confirmation"); - - eh = TAL_curl_easy_get (dh->url); - GNUNET_assert (NULL != (dh->json_enc = - json_dumps (deposit_confirmation_obj, - JSON_COMPACT))); - json_decref (deposit_confirmation_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for deposit-confirmation: `%s'\n", - dh->url); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDS, - dh->json_enc)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDSIZE, - strlen (dh->json_enc))); - ctx = MAH_handle_to_context (auditor); - dh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_deposit_confirmation_finished, - dh); - return dh; -} - - -/** - * Cancel a deposit-confirmation permission request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param deposit-confirmation the deposit-confirmation permission request handle - */ -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); - GNUNET_free (deposit_confirmation->json_enc); - GNUNET_free (deposit_confirmation); -} - - -/* end of auditor_api_deposit_confirmation.c */ diff --git a/src/auditor-lib/auditor_api_exchanges.c b/src/auditor-lib/auditor_api_exchanges.c @@ -1,245 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2018 GNUnet e.V. - - 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 auditor-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 "auditor_api_handle.h" -#include "taler_signatures.h" -#include "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 connection to auditor this request handle will use - */ - struct TALER_AUDITOR_Handle *auditor; - - /** - * 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 /deposit-confirmation 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; - - leh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - ja = json_object_get (json, - "exchanges"); - if ( (NULL == ja) || - (! json_is_array (ja)) ) - { - GNUNET_break (0); - response_code = 0; - break; - } - - ja_len = json_array_size (ja); - if (ja_len > MAX_EXCHANGES) - { - GNUNET_break (0); - response_code = 0; - break; - } - { - struct TALER_AUDITOR_ExchangeInfo ei[ja_len]; - int ok; - - ok = GNUNET_YES; - 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 = GNUNET_NO; - break; - } - } - if (GNUNET_YES != ok) - break; - leh->cb (leh->cb_cls, - response_code, - TALER_EC_NONE, - ja_len, - ei, - json); - leh->cb = NULL; - } - break; - 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 */ - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != leh->cb) - leh->cb (leh->cb_cls, - response_code, - TALER_JSON_get_error_code (json), - 0, - NULL, - json); - TALER_AUDITOR_list_exchanges_cancel (leh); -} - - -/** - * Submit an /exchanges request to the auditor and get the - * auditor's response. If the auditor's reply is not - * well-formed, we return an HTTP status code of zero to @a cb. - * - * @param auditor the auditor handle; the auditor must be ready to operate - * @param cb the callback to call when a reply for this request is available - * @param cb_cls closure for the above callback - * @return a handle for this request; NULL if the inputs are invalid (i.e. - * signatures fail to verify). In this case, the callback is not called. - */ -struct TALER_AUDITOR_ListExchangesHandle * -TALER_AUDITOR_list_exchanges (struct TALER_AUDITOR_Handle *auditor, - TALER_AUDITOR_ListExchangesResultCallback cb, - void *cb_cls) -{ - struct TALER_AUDITOR_ListExchangesHandle *leh; - struct GNUNET_CURL_Context *ctx; - CURL *eh; - - GNUNET_assert (GNUNET_YES == - MAH_handle_is_ready (auditor)); - - leh = GNUNET_new (struct TALER_AUDITOR_ListExchangesHandle); - leh->auditor = auditor; - leh->cb = cb; - leh->cb_cls = cb_cls; - leh->url = MAH_path_to_url (auditor, "/exchanges"); - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for list-exchanges: `%s'\n", - leh->url); - eh = TAL_curl_easy_get (leh->url); - ctx = MAH_handle_to_context (auditor); - leh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_NO, - &handle_exchanges_finished, - leh); - return leh; -} - - -/** - * Cancel a deposit-confirmation permission request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param deposit-confirmation the deposit-confirmation permission request handle - */ -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/auditor-lib/auditor_api_handle.c b/src/auditor-lib/auditor_api_handle.c @@ -1,527 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2018 GNUnet e.V. - - 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 auditor-lib/auditor_api_handle.c - * @brief Implementation of the "handle" component of 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_handle.h" -#include "curl_defaults.h" -#include "backoff.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)); - -/** - * Stages of initialization for the `struct TALER_AUDITOR_Handle` - */ -enum AuditorHandleState -{ - /** - * Just allocated. - */ - MHS_INIT = 0, - - /** - * Obtained the auditor's versioning data and version. - */ - MHS_VERSION = 1, - - /** - * Failed to initialize (fatal). - */ - MHS_FAILED = 2 -}; - - -/** - * Data for the request to get the /version of a auditor. - */ -struct VersionRequest; - - -/** - * Handle to the auditor - */ -struct TALER_AUDITOR_Handle -{ - /** - * The context of this handle - */ - struct GNUNET_CURL_Context *ctx; - - /** - * The URL of the auditor (i.e. "http://auditor.taler.net/") - */ - char *url; - - /** - * Function to call with the auditor's certification data, - * NULL if this has already been done. - */ - TALER_AUDITOR_VersionCallback version_cb; - - /** - * Closure to pass to @e version_cb. - */ - void *version_cb_cls; - - /** - * Data for the request to get the /version of a auditor, - * NULL once we are past stage #MHS_INIT. - */ - struct VersionRequest *vr; - - /** - * Task for retrying /version request. - */ - struct GNUNET_SCHEDULER_Task *retry_task; - - /** - * /version data of the auditor, only valid if - * @e handshake_complete is past stage #MHS_VERSION. - */ - struct TALER_AUDITOR_VersionInformation vi; - - /** - * Retry /version frequency. - */ - struct GNUNET_TIME_Relative retry_delay; - - /** - * Stage of the auditor's initialization routines. - */ - enum AuditorHandleState state; - -}; - - -/* ***************** Internal /version fetching ************* */ - -/** - * Data for the request to get the /version of a auditor. - */ -struct VersionRequest -{ - /** - * The connection to auditor this request handle will use - */ - struct TALER_AUDITOR_Handle *auditor; - - /** - * The url for this handle - */ - char *url; - - /** - * Entry for this request with the `struct GNUNET_CURL_Context`. - */ - struct GNUNET_CURL_Job *job; - -}; - - -/** - * Release memory occupied by a version request. - * Note that this does not cancel the request - * itself. - * - * @param vr request to free - */ -static void -free_version_request (struct VersionRequest *vr) -{ - GNUNET_free (vr->url); - GNUNET_free (vr); -} - - -/** - * Free version data object. - * - * @param vi data to free (pointer itself excluded) - */ -static void -free_version_info (struct TALER_AUDITOR_VersionInformation *vi) -{ - GNUNET_free_non_null (vi->version); - vi->version = NULL; -} - - -/** - * Decode the JSON in @a resp_obj from the /version response and store the data - * in the @a key_data. - * - * @param[in] resp_obj JSON object to parse - * @param check_sig #GNUNET_YES if we should check the signature - * @param[out] vi where to store the results we decoded - * @param[out] vc where to store version compatibility data - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error (malformed JSON) - */ -static int -decode_version_json (const json_t *resp_obj, - int check_sig, - struct TALER_AUDITOR_VersionInformation *vi, - enum TALER_AUDITOR_VersionCompatibility *vc) -{ - unsigned int age; - unsigned int revision; - unsigned int current; - const char *ver; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("version", - &ver), - GNUNET_JSON_spec_fixed_auto ("master_public_key", - &vi->auditor_pub), - GNUNET_JSON_spec_end() - }; - - if (JSON_OBJECT != json_typeof (resp_obj)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* check the version */ - if (GNUNET_OK != - GNUNET_JSON_parse (resp_obj, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (3 != sscanf (vi->version, - "%u:%u:%u", - &current, - &revision, - &age)) - { - GNUNET_break_op (0); - free_version_info (vi); - return GNUNET_SYSERR; - } - vi->version = GNUNET_strdup (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 GNUNET_OK; -} - - -/** - * Initiate download of /version from the auditor. - * - * @param cls auditor where to download /version from - */ -static void -request_version (void *cls); - - -/** - * Callback used when downloading the reply to a /version request - * is complete. - * - * @param cls the `struct VersionRequest` - * @param response_code HTTP response code, 0 on error - * @param resp_obj parsed JSON result, NULL on error - */ -static void -version_completed_cb (void *cls, - long response_code, - const void *gresp_obj) -{ - const json_t *resp_obj = gresp_obj; - struct VersionRequest *vr = cls; - struct TALER_AUDITOR_Handle *auditor = vr->auditor; - enum TALER_AUDITOR_VersionCompatibility vc; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Received version from URL `%s' with status %ld.\n", - vr->url, - response_code); - vc = TALER_AUDITOR_VC_PROTOCOL_ERROR; - switch (response_code) - { - case 0: - case MHD_HTTP_INTERNAL_SERVER_ERROR: - free_version_request (vr); - auditor->vr = NULL; - GNUNET_assert (NULL == auditor->retry_task); - auditor->retry_delay = AUDITOR_LIB_BACKOFF (auditor->retry_delay); - auditor->retry_task = GNUNET_SCHEDULER_add_delayed (auditor->retry_delay, - &request_version, - auditor); - return; - case MHD_HTTP_OK: - if (NULL == resp_obj) - { - response_code = 0; - break; - } - if (GNUNET_OK != - decode_version_json (resp_obj, - GNUNET_YES, - &auditor->vi, - &vc)) - { - response_code = 0; - break; - } - auditor->retry_delay = GNUNET_TIME_UNIT_ZERO; - break; - default: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - break; - } - if (MHD_HTTP_OK != response_code) - { - auditor->vr = NULL; - free_version_request (vr); - auditor->state = MHS_FAILED; - free_version_info (&auditor->vi); - /* notify application that we failed */ - auditor->version_cb (auditor->version_cb_cls, - NULL, - vc); - return; - } - - auditor->vr = NULL; - free_version_request (vr); - auditor->state = MHS_VERSION; - /* notify application about the key information */ - auditor->version_cb (auditor->version_cb_cls, - &auditor->vi, - vc); -} - - -/* ********************* library internal API ********* */ - - -/** - * Get the context of a auditor. - * - * @param h the auditor handle to query - * @return ctx context to execute jobs in - */ -struct GNUNET_CURL_Context * -MAH_handle_to_context (struct TALER_AUDITOR_Handle *h) -{ - return h->ctx; -} - - -/** - * Check if the handle is ready to process requests. - * - * @param h the auditor handle to query - * @return #GNUNET_YES if we are ready, #GNUNET_NO if not - */ -int -MAH_handle_is_ready (struct TALER_AUDITOR_Handle *h) -{ - return (MHS_VERSION == h->state) ? GNUNET_YES : GNUNET_NO; -} - - -/** - * Obtain the URL to use for an API request. - * - * @param h handle for the auditor - * @param path Taler API path (i.e. "/deposit-confirmation") - * @return the full URL to use with cURL - */ -char * -MAH_path_to_url (struct TALER_AUDITOR_Handle *h, - const char *path) -{ - return MAH_path_to_url2 (h->url, - path); -} - - -/** - * Obtain the URL to use for an API request. - * - * @param base_url base URL of the auditor (i.e. "http://auditor/") - * @param path Taler API path (i.e. "/deposit-confirmation") - * @return the full URL to use with cURL - */ -char * -MAH_path_to_url2 (const char *base_url, - const char *path) -{ - char *url; - - if ( ('/' == path[0]) && - (0 < strlen (base_url)) && - ('/' == base_url[strlen (base_url) - 1]) ) - path++; /* avoid generating URL with "//" from concat */ - GNUNET_asprintf (&url, - "%s%s", - base_url, - path); - return url; -} - - -/* ********************* public API ******************* */ - - -/** - * Initialise a connection to the auditor. Will connect to the - * auditor and obtain information about the auditor's master public - * key and the auditor's auditor. The respective information will - * be passed to the @a version_cb once available, and all future - * interactions with the auditor will be checked to be signed - * (where appropriate) by the respective master key. - * - * @param ctx the context - * @param url HTTP base URL for the auditor - * @param version_cb function to call with the auditor's version information - * @param version_cb_cls closure for @a version_cb - * @return the auditor handle; NULL upon error - */ -struct TALER_AUDITOR_Handle * -TALER_AUDITOR_connect (struct GNUNET_CURL_Context *ctx, - const char *url, - TALER_AUDITOR_VersionCallback version_cb, - void *version_cb_cls) -{ - struct TALER_AUDITOR_Handle *auditor; - - auditor = GNUNET_new (struct TALER_AUDITOR_Handle); - auditor->ctx = ctx; - auditor->url = GNUNET_strdup (url); - auditor->version_cb = version_cb; - auditor->version_cb_cls = version_cb_cls; - auditor->retry_task = GNUNET_SCHEDULER_add_now (&request_version, - auditor); - return auditor; -} - - -/** - * Initiate download of /version from the auditor. - * - * @param cls auditor where to download /version from - */ -static void -request_version (void *cls) -{ - struct TALER_AUDITOR_Handle *auditor = cls; - struct VersionRequest *vr; - CURL *eh; - - auditor->retry_task = NULL; - GNUNET_assert (NULL == auditor->vr); - vr = GNUNET_new (struct VersionRequest); - vr->auditor = auditor; - vr->url = MAH_path_to_url (auditor, - "/version"); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting version with URL `%s'.\n", - vr->url); - eh = TAL_curl_easy_get (vr->url); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_VERBOSE, - 0)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_TIMEOUT, - (long) 300)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_HEADERDATA, - vr)); - vr->job = GNUNET_CURL_job_add (auditor->ctx, - eh, - GNUNET_NO, - &version_completed_cb, - vr); - auditor->vr = vr; -} - - -/** - * Disconnect from the auditor - * - * @param auditor the auditor handle - */ -void -TALER_AUDITOR_disconnect (struct TALER_AUDITOR_Handle *auditor) -{ - if (NULL != auditor->vr) - { - GNUNET_CURL_job_cancel (auditor->vr->job); - free_version_request (auditor->vr); - auditor->vr = NULL; - } - free_version_info (&auditor->vi); - if (NULL != auditor->retry_task) - { - GNUNET_SCHEDULER_cancel (auditor->retry_task); - auditor->retry_task = NULL; - } - GNUNET_free (auditor->url); - GNUNET_free (auditor); -} - - -/* end of auditor_api_handle.c */ diff --git a/src/auditor-lib/backoff.h b/src/auditor-lib/backoff.h @@ -1,38 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2018 GNUnet e.V. - - 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 auditor-lib/backoff.h - * @brief backoff computation for the auditor lib - * @author Florian Dold - */ - - -#ifndef _TALER_BACKOFF_H -#define _TALER_BACKOFF_H - -#include "platform.h" -#include <gnunet/gnunet_time_lib.h> - -/** - * Random exponential backoff used in the auditor lib. - */ -#define AUDITOR_LIB_BACKOFF(r) GNUNET_TIME_randomized_backoff ( \ - (r), \ - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 2)); - -#endif diff --git a/src/auditor-lib/curl_defaults.c b/src/auditor-lib/curl_defaults.c @@ -1,75 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2018 GNUnet e.V. - - 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 auditor-lib/curl_defaults.c - * @brief curl easy handle defaults - * @author Florian Dold - */ - -#include "curl_defaults.h" - - -/** - * Get a curl handle with the right defaults - * for the exchange lib. In the future, we might manage a pool of connections here. - * - * @param url URL to query - */ -CURL * -TAL_curl_easy_get (const char *url) -{ - CURL *eh; - - eh = curl_easy_init (); - - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_URL, - url)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_ENCODING, - "deflate")); -#ifdef CURLOPT_TCP_FASTOPEN - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_TCP_FASTOPEN, - 1L)); -#endif - { - /* Unfortunately libcurl needs chunk to be alive until after - curl_easy_perform. To avoid manual cleanup, we keep - one static list here. */ - static struct curl_slist *chunk = NULL; - if (NULL == chunk) - { - /* With POST requests, we do not want to wait for the - "100 Continue" response, as our request bodies are usually - small and directy sending them saves us a round trip. - - Clearing the expect header like this disables libcurl's - default processing of the header. - - Disabling this header is safe for other HTTP methods, thus - we don't distinguish further before setting it. */ - chunk = curl_slist_append (chunk, "Expect:"); - } - GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HTTPHEADER, chunk)); - } - - return eh; -} diff --git a/src/auditor-lib/test_exchange_api_home/.local/share/taler/auditor/offline-keys/auditor.priv b/src/auditor-lib/test_exchange_api_home/.local/share/taler/auditor/offline-keys/auditor.priv @@ -1 +0,0 @@ -~—}Kß+*hm»Ě^Ý-×á{śÝ{Śü0ÍZ4” -\ No newline at end of file diff --git a/src/auditor-lib/test_exchange_api_home/.local/share/taler/exchange/wirefees/x-taler-bank.fee b/src/auditor-lib/test_exchange_api_home/.local/share/taler/exchange/wirefees/x-taler-bank.fee Binary files differ. diff --git a/src/auditor-lib/testing_auditor_api_cmd_deposit_confirmation.c b/src/auditor-lib/testing_auditor_api_cmd_deposit_confirmation.c @@ -1,411 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 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 auditor-lib/testing_auditor_api_cmd_deposit_confirmation.c - * @brief command for testing /deposit_confirmation. - * @author Christian Grothoff - */ - -#include "platform.h" -#include "taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include "taler_auditor_service.h" -#include "taler_testing_lib.h" -#include "taler_signatures.h" -#include "backoff.h" - - -/** - * State for a "deposit confirmation" CMD. - */ -struct DepositConfirmationState -{ - - /** - * Reference to any command that is able to provide a deposit. - */ - const char *deposit_reference; - - /** - * What is the deposited amount without the fee (i.e. the - * amount we expect in the deposit confirmation)? - */ - const char *amount_without_fee; - - /** - * Which coin of the @e deposit_reference should we confirm. - */ - unsigned int coin_index; - - /** - * DepositConfirmation handle while operation is running. - */ - struct TALER_AUDITOR_DepositConfirmationHandle *dc; - - /** - * Auditor connection. - */ - struct TALER_AUDITOR_Handle *auditor; - - /** - * Interpreter state. - */ - struct TALER_TESTING_Interpreter *is; - - /** - * Task scheduled to try later. - */ - struct GNUNET_SCHEDULER_Task *retry_task; - - /** - * How long do we wait until we retry? - */ - struct GNUNET_TIME_Relative backoff; - - /** - * Expected HTTP response code. - */ - unsigned int expected_response_code; - - /** - * Should we retry on (transient) failures? - */ - int do_retry; - -}; - - -/** - * Run the command. - * - * @param cls closure. - * @param cmd the command to execute. - * @param is the interpreter state. - */ -static void -deposit_confirmation_run (void *cls, - const struct TALER_TESTING_Command *cmd, - struct TALER_TESTING_Interpreter *is); - - -/** - * Task scheduled to re-try #deposit_confirmation_run. - * - * @param cls a `struct DepositConfirmationState` - */ -static void -do_retry (void *cls) -{ - struct DepositConfirmationState *dcs = cls; - - dcs->retry_task = NULL; - deposit_confirmation_run (dcs, - NULL, - dcs->is); -} - - -/** - * Callback to analyze the /deposit-confirmation response, just used - * to check if the response code is acceptable. - * - * @param cls closure. - * @param http_status HTTP response code. - * @param ec taler-specific error code. - * @param obj raw response from the auditor. - */ -static void -deposit_confirmation_cb (void *cls, - unsigned int http_status, - enum TALER_ErrorCode ec, - const json_t *obj) -{ - struct DepositConfirmationState *dcs = cls; - - dcs->dc = NULL; - if (dcs->expected_response_code != http_status) - { - if (GNUNET_YES == dcs->do_retry) - { - if ( (0 == http_status) || - (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || - (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Retrying deposit confirmation failed with %u/%d\n", - http_status, - (int) ec); - /* on DB conflicts, do not use backoff */ - if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) - dcs->backoff = GNUNET_TIME_UNIT_ZERO; - else - dcs->backoff = AUDITOR_LIB_BACKOFF (dcs->backoff); - dcs->retry_task = GNUNET_SCHEDULER_add_delayed (dcs->backoff, - &do_retry, - dcs); - return; - } - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u to command %s in %s:%u\n", - http_status, - dcs->is->commands[dcs->is->ip].label, - __FILE__, - __LINE__); - json_dumpf (obj, stderr, 0); - TALER_TESTING_interpreter_fail (dcs->is); - return; - } - TALER_TESTING_interpreter_next (dcs->is); -} - - -/** - * Run the command. - * - * @param cls closure. - * @param cmd the command to execute. - * @param is the interpreter state. - */ -static void -deposit_confirmation_run (void *cls, - const struct TALER_TESTING_Command *cmd, - struct TALER_TESTING_Interpreter *is) -{ - struct DepositConfirmationState *dcs = cls; - const struct TALER_TESTING_Command *deposit_cmd; - struct GNUNET_HashCode h_wire; - struct GNUNET_HashCode h_contract_terms; - struct GNUNET_TIME_Absolute timestamp; - struct GNUNET_TIME_Absolute refund_deadline; - struct TALER_Amount amount_without_fee; - struct TALER_CoinSpendPublicKeyP coin_pub; - const struct GNUNET_CRYPTO_EddsaPrivateKey *merchant_priv; - struct TALER_MerchantPublicKeyP merchant_pub; - const struct TALER_ExchangePublicKeyP *exchange_pub; - const struct TALER_ExchangeSignatureP *exchange_sig; - const json_t *wire_details; - const json_t *contract_terms; - const struct TALER_CoinSpendPrivateKeyP *coin_priv; - const struct TALER_EXCHANGE_Keys *keys; - const struct TALER_EXCHANGE_SigningPublicKey *spk; - - dcs->is = is; - GNUNET_assert (NULL != dcs->deposit_reference); - deposit_cmd - = TALER_TESTING_interpreter_lookup_command (is, - dcs->deposit_reference); - if (NULL == deposit_cmd) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (is); - return; - } - - GNUNET_assert (GNUNET_OK == - TALER_TESTING_get_trait_exchange_pub (deposit_cmd, - dcs->coin_index, - &exchange_pub)); - GNUNET_assert (GNUNET_OK == - TALER_TESTING_get_trait_exchange_sig (deposit_cmd, - dcs->coin_index, - &exchange_sig)); - keys = TALER_EXCHANGE_get_keys (dcs->is->exchange); - GNUNET_assert (NULL != keys); - spk = TALER_EXCHANGE_get_exchange_signing_key_info (keys, - exchange_pub); - - GNUNET_assert (GNUNET_OK == - TALER_TESTING_get_trait_contract_terms (deposit_cmd, - dcs->coin_index, - &contract_terms)); - /* Very unlikely to fail */ - GNUNET_assert (NULL != contract_terms); - TALER_JSON_hash (contract_terms, - &h_contract_terms); - GNUNET_assert (GNUNET_OK == - TALER_TESTING_get_trait_wire_details (deposit_cmd, - dcs->coin_index, - &wire_details)); - TALER_JSON_hash (wire_details, - &h_wire); - - GNUNET_assert (GNUNET_OK == - TALER_TESTING_get_trait_coin_priv (deposit_cmd, - dcs->coin_index, - &coin_priv)); - GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, - &coin_pub.eddsa_pub); - GNUNET_assert (GNUNET_OK == - TALER_TESTING_get_trait_peer_key (deposit_cmd, - dcs->coin_index, - &merchant_priv)); - GNUNET_CRYPTO_eddsa_key_get_public (merchant_priv, - &merchant_pub.eddsa_pub); - GNUNET_assert (GNUNET_OK == - TALER_string_to_amount (dcs->amount_without_fee, - &amount_without_fee)); - - dcs->dc = TALER_AUDITOR_deposit_confirmation - (dcs->auditor, - &h_wire, - &h_contract_terms, - timestamp, - refund_deadline, - &amount_without_fee, - &coin_pub, - &merchant_pub, - exchange_pub, - exchange_sig, - &keys->master_pub, - spk->valid_from, - spk->valid_until, - spk->valid_legal, - &spk->master_sig, - &deposit_confirmation_cb, - dcs); - - if (NULL == dcs->dc) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (is); - return; - } - return; -} - - -/** - * Free the state of a "deposit_confirmation" CMD, and possibly cancel a - * pending operation thereof. - * - * @param cls closure, a `struct DepositConfirmationState` - * @param cmd the command which is being cleaned up. - */ -static void -deposit_confirmation_cleanup (void *cls, - const struct TALER_TESTING_Command *cmd) -{ - struct DepositConfirmationState *dcs = cls; - - if (NULL != dcs->dc) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Command %u (%s) did not complete\n", - dcs->is->ip, - cmd->label); - TALER_AUDITOR_deposit_confirmation_cancel (dcs->dc); - dcs->dc = NULL; - } - if (NULL != dcs->retry_task) - { - GNUNET_SCHEDULER_cancel (dcs->retry_task); - dcs->retry_task = NULL; - } - GNUNET_free (dcs); -} - - -/** - * Offer internal data to other commands. - * - * @param cls closure. - * @param ret[out] set to the wanted data. - * @param trait name of the trait. - * @param index index number of the traits to be returned. - * - * @return #GNUNET_OK on success - */ -static int -deposit_confirmation_traits (void *cls, - const void **ret, - const char *trait, - unsigned int index) -{ - /* Must define this function because some callbacks - * look for certain traits on _all_ the commands. */ - return GNUNET_SYSERR; -} - - -/** - * Create a "deposit-confirmation" command. - * - * @param label command label. - * @param auditor auditor connection. - * @param deposit_reference reference to any operation that can - * provide a coin. - * @param coin_index if @a deposit_reference offers an array of - * coins, this parameter selects which one in that array. - * This value is currently ignored, as only one-coin - * deposits are implemented. - * @param amount_without_fee deposited amount without the fee - * @param expected_response_code expected HTTP response code. - * @return the command. - */ -struct TALER_TESTING_Command -TALER_TESTING_cmd_deposit_confirmation - (const char *label, - struct TALER_AUDITOR_Handle *auditor, - const char *deposit_reference, - unsigned int coin_index, - const char *amount_without_fee, - unsigned int expected_response_code) -{ - struct TALER_TESTING_Command cmd = {0}; /* need explicit zeroing..*/ - struct DepositConfirmationState *dcs; - - dcs = GNUNET_new (struct DepositConfirmationState); - dcs->auditor = auditor; - dcs->deposit_reference = deposit_reference; - dcs->coin_index = coin_index; - dcs->amount_without_fee = amount_without_fee; - dcs->expected_response_code = expected_response_code; - - cmd.cls = dcs; - cmd.label = label; - cmd.run = &deposit_confirmation_run; - cmd.cleanup = &deposit_confirmation_cleanup; - cmd.traits = &deposit_confirmation_traits; - - return cmd; -} - - -/** - * Modify a deposit confirmation command to enable retries when we get - * transient errors from the auditor. - * - * @param cmd a deposit confirmation command - * @return the command with retries enabled - */ -struct TALER_TESTING_Command -TALER_TESTING_cmd_deposit_confirmation_with_retry (struct TALER_TESTING_Command cmd) -{ - struct DepositConfirmationState *dcs; - - GNUNET_assert (&deposit_confirmation_run == cmd.run); - dcs = cmd.cls; - dcs->do_retry = GNUNET_YES; - return cmd; -} - - -/* end of testing_auditor_api_cmd_deposit_confirmation.c */ diff --git a/src/auditor-lib/testing_auditor_api_cmd_exchanges.c b/src/auditor-lib/testing_auditor_api_cmd_exchanges.c @@ -1,296 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 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 auditor-lib/testing_auditor_api_cmd_exchanges.c - * @brief command for testing /exchanges. - * @author Christian Grothoff - */ -#include "platform.h" -#include "taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include "taler_auditor_service.h" -#include "taler_testing_lib.h" -#include "taler_signatures.h" -#include "backoff.h" - - -/** - * State for a "deposit confirmation" CMD. - */ -struct ExchangesState -{ - - /** - * Exchanges handle while operation is running. - */ - struct TALER_AUDITOR_ListExchangesHandle *leh; - - /** - * Auditor connection. - */ - struct TALER_AUDITOR_Handle *auditor; - - /** - * Interpreter state. - */ - struct TALER_TESTING_Interpreter *is; - - /** - * Task scheduled to try later. - */ - struct GNUNET_SCHEDULER_Task *retry_task; - - /** - * How long do we wait until we retry? - */ - struct GNUNET_TIME_Relative backoff; - - /** - * Expected HTTP response code. - */ - unsigned int expected_response_code; - - /** - * Should we retry on (transient) failures? - */ - int do_retry; - -}; - - -/** - * Run the command. - * - * @param cls closure. - * @param cmd the command to execute. - * @param is the interpreter state. - */ -static void -exchanges_run (void *cls, - const struct TALER_TESTING_Command *cmd, - struct TALER_TESTING_Interpreter *is); - - -/** - * Task scheduled to re-try #exchanges_run. - * - * @param cls a `struct ExchangesState` - */ -static void -do_retry (void *cls) -{ - struct ExchangesState *es = cls; - - es->retry_task = NULL; - exchanges_run (es, - NULL, - es->is); -} - - -/** - * Callback to analyze the /exchanges response. - * - * @param cls closure. - * @param http_status HTTP response code. - * @param ec taler-specific error code. - * @param obj raw response from the auditor. - */ -static void -exchanges_cb (void *cls, - unsigned int http_status, - enum TALER_ErrorCode ec, - unsigned int num_exchanges, - const struct TALER_AUDITOR_ExchangeInfo *ei, - const json_t *raw_response) -{ - struct ExchangesState *es = cls; - - es->leh = NULL; - if (es->expected_response_code != http_status) - { - if (GNUNET_YES == es->do_retry) - { - if ( (0 == http_status) || - (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || - (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Retrying list exchanges failed with %u/%d\n", - http_status, - (int) ec); - /* on DB conflicts, do not use backoff */ - if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) - es->backoff = GNUNET_TIME_UNIT_ZERO; - else - es->backoff = AUDITOR_LIB_BACKOFF (es->backoff); - es->retry_task = GNUNET_SCHEDULER_add_delayed (es->backoff, - &do_retry, - es); - return; - } - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u to command %s in %s:%u\n", - http_status, - es->is->commands[es->is->ip].label, - __FILE__, - __LINE__); - json_dumpf (raw_response, stderr, 0); - TALER_TESTING_interpreter_fail (es->is); - return; - } - TALER_TESTING_interpreter_next (es->is); -} - - -/** - * Run the command. - * - * @param cls closure. - * @param cmd the command to execute. - * @param is the interpreter state. - */ -static void -exchanges_run (void *cls, - const struct TALER_TESTING_Command *cmd, - struct TALER_TESTING_Interpreter *is) -{ - struct ExchangesState *es = cls; - - es->is = is; - es->leh = TALER_AUDITOR_list_exchanges - (es->auditor, - &exchanges_cb, - es); - - if (NULL == es->leh) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (is); - return; - } - return; -} - - -/** - * Free the state of a "exchanges" CMD, and possibly cancel a - * pending operation thereof. - * - * @param cls closure, a `struct ExchangesState` - * @param cmd the command which is being cleaned up. - */ -static void -exchanges_cleanup (void *cls, - const struct TALER_TESTING_Command *cmd) -{ - struct ExchangesState *es = cls; - - if (NULL != es->leh) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Command %u (%s) did not complete\n", - es->is->ip, - cmd->label); - TALER_AUDITOR_list_exchanges_cancel (es->leh); - es->leh = NULL; - } - if (NULL != es->retry_task) - { - GNUNET_SCHEDULER_cancel (es->retry_task); - es->retry_task = NULL; - } - GNUNET_free (es); -} - - -/** - * Offer internal data to other commands. - * - * @param cls closure. - * @param ret[out] set to the wanted data. - * @param trait name of the trait. - * @param index index number of the traits to be returned. - * - * @return #GNUNET_OK on success - */ -static int -exchanges_traits (void *cls, - const void **ret, - const char *trait, - unsigned int index) -{ - /* Must define this function because some callbacks - * look for certain traits on _all_ the commands. */ - return GNUNET_SYSERR; -} - - -/** - * Create a "list exchanges" command. - * - * @param label command label. - * @param auditor auditor connection. - * @param expected_response_code expected HTTP response code. - * @return the command. - */ -struct TALER_TESTING_Command -TALER_TESTING_cmd_exchanges - (const char *label, - struct TALER_AUDITOR_Handle *auditor, - unsigned int expected_response_code) -{ - struct TALER_TESTING_Command cmd = {0}; /* need explicit zeroing..*/ - struct ExchangesState *es; - - es = GNUNET_new (struct ExchangesState); - es->auditor = auditor; - es->expected_response_code = expected_response_code; - - cmd.cls = es; - cmd.label = label; - cmd.run = &exchanges_run; - cmd.cleanup = &exchanges_cleanup; - cmd.traits = &exchanges_traits; - - return cmd; -} - - -/** - * Modify an exchanges command to enable retries when we get - * transient errors from the auditor. - * - * @param cmd a deposit confirmation command - * @return the command with retries enabled - */ -struct TALER_TESTING_Command -TALER_TESTING_cmd_exchanges_with_retry (struct TALER_TESTING_Command cmd) -{ - struct ExchangesState *es; - - GNUNET_assert (&exchanges_run == cmd.run); - es = cmd.cls; - es->do_retry = GNUNET_YES; - return cmd; -} - - -/* end of testing_auditor_api_cmd_exchanges.c */ diff --git a/src/benchmark/Makefile.am b/src/benchmark/Makefile.am @@ -20,8 +20,8 @@ taler_exchange_benchmark_LDADD = \ $(top_builddir)/src/wire/libtalerwire.la \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ - $(top_builddir)/src/exchange-lib/libtalerexchange.la \ - $(top_builddir)/src/exchange-lib/libtalertesting.la \ + $(top_builddir)/src/lib/libtalerexchange.la \ + $(top_builddir)/src/lib/libtalertesting.la \ $(top_builddir)/src/bank-lib/libtalerfakebank.la \ $(top_builddir)/src/bank-lib/libtalerbank.la \ $(top_builddir)/src/bank-lib/libtalerbanktesting.la \ diff --git a/src/exchange-lib/Makefile.am b/src/exchange-lib/Makefile.am @@ -1,181 +0,0 @@ -# This Makefile.am is in the public domain -AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/bank-lib - -if USE_COVERAGE - AM_CFLAGS = --coverage -O0 - XLIB = -lgcov -endif - -lib_LTLIBRARIES = \ - libtalerexchange.la \ - libtalertesting.la - -libtalerexchange_la_LDFLAGS = \ - -version-info 4:0:0 \ - -no-undefined -libtalerexchange_la_SOURCES = \ - curl_defaults.c \ - exchange_api_common.c \ - exchange_api_handle.c exchange_api_handle.h \ - exchange_api_deposit.c \ - exchange_api_payback.c \ - exchange_api_refresh.c \ - exchange_api_refresh_link.c \ - exchange_api_refund.c \ - exchange_api_reserve.c \ - exchange_api_track_transaction.c \ - exchange_api_track_transfer.c \ - exchange_api_wire.c -libtalerexchange_la_LIBADD = \ - $(top_builddir)/src/auditor-lib/libtalerauditor.la \ - $(top_builddir)/src/json/libtalerjson.la \ - $(top_builddir)/src/util/libtalerutil.la \ - -lgnunetcurl \ - -lgnunetjson \ - -lgnunetutil \ - -ljansson \ - $(XLIB) - -libtalertesting_la_LDFLAGS = \ - -version-info 0:0:0 \ - -no-undefined -libtalertesting_la_SOURCES = \ - curl_defaults.c \ - testing_api_cmd_exec_aggregator.c \ - testing_api_cmd_exec_wirewatch.c \ - testing_api_cmd_exec_keyup.c \ - testing_api_cmd_exec_auditor-sign.c \ - testing_api_cmd_fakebank_transfer.c \ - testing_api_cmd_withdraw.c \ - testing_api_cmd_wire.c \ - testing_api_cmd_refund.c \ - testing_api_cmd_status.c \ - testing_api_cmd_deposit.c \ - testing_api_cmd_sleep.c \ - testing_api_cmd_refresh.c \ - testing_api_cmd_track.c \ - testing_api_cmd_bank_check.c \ - testing_api_cmd_payback.c \ - testing_api_cmd_signal.c \ - testing_api_cmd_check_keys.c \ - testing_api_cmd_batch.c \ - testing_api_cmd_serialize_keys.c \ - testing_api_helpers.c \ - testing_api_loop.c \ - testing_api_traits.c \ - testing_api_trait_blinding_key.c \ - testing_api_trait_coin_priv.c \ - testing_api_trait_denom_pub.c \ - testing_api_trait_denom_sig.c \ - testing_api_trait_exchange_pub.c \ - testing_api_trait_exchange_sig.c \ - testing_api_trait_json.c \ - testing_api_trait_process.c \ - testing_api_trait_reserve_priv.c \ - testing_api_trait_number.c \ - testing_api_trait_fresh_coin.c \ - testing_api_trait_string.c \ - testing_api_trait_key_peer.c \ - testing_api_trait_wtid.c \ - testing_api_trait_amount.c \ - testing_api_trait_cmd.c -libtalertesting_la_LIBADD = \ - libtalerexchange.la \ - $(top_builddir)/src/wire/libtalerwire.la \ - $(top_builddir)/src/json/libtalerjson.la \ - $(top_builddir)/src/util/libtalerutil.la \ - $(top_builddir)/src/bank-lib/libtalerbank.la \ - $(top_builddir)/src/bank-lib/libtalerfakebank.la \ - -lgnunetcurl \ - -lgnunetjson \ - -lgnunetutil \ - -ljansson \ - $(XLIB) - -if HAVE_LIBCURL -libtalerexchange_la_LIBADD += -lcurl -else -if HAVE_LIBGNURL -libtalerexchange_la_LIBADD += -lgnurl -endif -endif - -check_PROGRAMS = \ - test_exchange_api_keys_cherry_picking_new \ - test_exchange_api_overlapping_keys_bug \ - test_exchange_api_new - -if HAVE_TWISTER - check_PROGRAMS += \ - test_exchange_api_twisted - -test_exchange_api_twisted_SOURCES = \ - test_exchange_api_twisted.c -test_exchange_api_twisted_LDADD = \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/exchange-lib/libtalertesting.la \ - $(top_builddir)/src/bank-lib/libtalerfakebank.la \ - $(top_builddir)/src/bank-lib/libtalerbank.la \ - $(top_builddir)/src/exchange-lib/libtalerexchange.la \ - $(top_builddir)/src/json/libtalerjson.la \ - $(top_builddir)/src/util/libtalerutil.la \ - -ltalertwistertesting \ - -lgnunetjson \ - -lgnunetcurl \ - -lgnunetutil \ - -ljansson - -endif - -AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; - -TESTS = \ - $(check_PROGRAMS) - -test_exchange_api_new_SOURCES = \ - test_exchange_api_new.c -test_exchange_api_new_LDADD = \ - libtalertesting.la \ - libtalerexchange.la \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/bank-lib/libtalerfakebank.la \ - $(top_builddir)/src/bank-lib/libtalerbank.la \ - $(top_builddir)/src/json/libtalerjson.la \ - $(top_builddir)/src/util/libtalerutil.la \ - -lgnunetcurl \ - -lgnunetutil \ - -ljansson - -test_exchange_api_overlapping_keys_bug_SOURCES = \ - test_exchange_api_overlapping_keys_bug.c -test_exchange_api_overlapping_keys_bug_LDADD = \ - libtalertesting.la \ - libtalerexchange.la \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/json/libtalerjson.la \ - $(top_builddir)/src/util/libtalerutil.la \ - $(top_builddir)/src/bank-lib/libtalerbank.la \ - -lgnunetcurl \ - -lgnunetutil \ - -ljansson - -test_exchange_api_keys_cherry_picking_new_SOURCES = \ - test_exchange_api_keys_cherry_picking_new.c -test_exchange_api_keys_cherry_picking_new_LDADD = \ - libtalertesting.la \ - libtalerexchange.la \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/json/libtalerjson.la \ - $(top_builddir)/src/util/libtalerutil.la \ - $(top_builddir)/src/bank-lib/libtalerbank.la \ - -lgnunetcurl \ - -lgnunetutil \ - -ljansson - -EXTRA_DIST = \ - test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv \ - test_exchange_api_home/.config/taler/test.json \ - test_exchange_api_home/.config/taler/sepa.json \ - test_exchange_api.conf \ - test_exchange_api_keys_cherry_picking.conf \ - test_exchange_api_keys_cherry_picking_extended.conf diff --git a/src/exchange-lib/curl_defaults.c b/src/exchange-lib/curl_defaults.c @@ -1,75 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2018 GNUnet e.V. - - 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 exchange-lib/curl_defaults.c - * @brief curl easy handle defaults - * @author Florian Dold - */ - -#include "curl_defaults.h" - - -/** - * Get a curl handle with the right defaults - * for the exchange lib. In the future, we might manage a pool of connections here. - * - * @param url URL to query - */ -CURL * -TEL_curl_easy_get (const char *url) -{ - CURL *eh; - - eh = curl_easy_init (); - - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_URL, - url)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_ENCODING, - "deflate")); -#ifdef CURLOPT_TCP_FASTOPEN - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_TCP_FASTOPEN, - 1L)); -#endif - { - /* Unfortunately libcurl needs chunk to be alive until after - curl_easy_perform. To avoid manual cleanup, we keep - one static list here. */ - static struct curl_slist *chunk = NULL; - if (NULL == chunk) - { - /* With POST requests, we do not want to wait for the - "100 Continue" response, as our request bodies are usually - small and directy sending them saves us a round trip. - - Clearing the expect header like this disables libcurl's - default processing of the header. - - Disabling this header is safe for other HTTP methods, thus - we don't distinguish further before setting it. */ - chunk = curl_slist_append (chunk, "Expect:"); - } - GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HTTPHEADER, chunk)); - } - - return eh; -} diff --git a/src/exchange-lib/curl_defaults.h b/src/exchange-lib/curl_defaults.h @@ -1,41 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2018 GNUnet e.V. - - 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 exchange-lib/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 exchange lib. In the future, we might manage a pool of connections here. - * - * @param url URL to query - */ -CURL * -TEL_curl_easy_get (const char *url); - -#endif /* _TALER_CURL_DEFAULTS_H */ diff --git a/src/exchange-lib/exchange_api_deposit.c b/src/exchange-lib/exchange_api_deposit.c @@ -1,597 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015, 2018 GNUnet e.V. - - 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 exchange-lib/exchange_api_deposit.c - * @brief Implementation of the /deposit request of the exchange's HTTP API - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - * @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_exchange_service.h" -#include "exchange_api_handle.h" -#include "taler_signatures.h" -#include "curl_defaults.h" - - -/** - * @brief A Deposit Handle - */ -struct TALER_EXCHANGE_DepositHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * JSON encoding of the request to POST. - */ - char *json_enc; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_DepositResultCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Information the exchange should sign in response. - */ - struct TALER_DepositConfirmationPS depconf; - - /** - * Value of the /deposit transaction, including fee. - */ - struct TALER_Amount amount_with_fee; - - /** - * Total value of the coin being transacted with. - */ - struct TALER_Amount coin_value; - -}; - - -/** - * Signature of functions called with the result from our call to the - * auditor's /deposit-confirmation handler. - * - * @param cls closure - * @param http_status HTTP status code, 200 on success - * @param ec taler protocol error status code, 0 on success - * @param json raw json response - */ -static void -acc_confirmation_cb (void *cls, - unsigned int http_status, - enum TALER_ErrorCode ec, - const json_t *json) -{ - /* FIXME: clean up state, some logging on errors! */ -} - - -/** - * Verify that the signature on the "200 OK" response - * from the exchange is valid. - * - * @param dh deposit handle - * @param json json reply with the signature - * @param exchange_sig[out] set to the exchange's signature - * @param exchange_pub[out] set to the exchange's public key - * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not - */ -static int -verify_deposit_signature_ok (const struct TALER_EXCHANGE_DepositHandle *dh, - const json_t *json, - struct TALER_ExchangeSignatureP *exchange_sig, - struct TALER_ExchangePublicKeyP *exchange_pub) -{ - const struct TALER_EXCHANGE_Keys *key_state; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("sig", exchange_sig), - GNUNET_JSON_spec_fixed_auto ("pub", exchange_pub), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - key_state = TALER_EXCHANGE_get_keys (dh->exchange); - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_state, - exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT, - &dh->depconf.purpose, - &exchange_sig->eddsa_signature, - &exchange_pub->eddsa_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 /* #5447: replace with "for all auditors, if auditor selected for DC notification... */) - { - struct TALER_AUDITOR_DepositConfirmationHandle *dch; - const struct TALER_EXCHANGE_SigningPublicKey *spk; - struct TALER_Amount amount_without_fee; - - spk = TALER_EXCHANGE_get_signing_key_details (key_state, - exchange_pub); - GNUNET_assert (NULL != spk); - TALER_amount_ntoh (&amount_without_fee, - &dh->depconf.amount_without_fee); - dch = TALER_AUDITOR_deposit_confirmation (NULL /* FIXME: auditor */, - &dh->depconf.h_wire, - &dh->depconf.h_contract_terms, - GNUNET_TIME_absolute_ntoh (dh->depconf.timestamp), - GNUNET_TIME_absolute_ntoh (dh->depconf.refund_deadline), - &amount_without_fee, - &dh->depconf.coin_pub, - &dh->depconf.merchant, - exchange_pub, - exchange_sig, - &key_state->master_pub, - spk->valid_from, - spk->valid_until, - spk->valid_legal, - &spk->master_sig, - &acc_confirmation_cb, - NULL /* FIXME: context! */); - } - - - return GNUNET_OK; -} - - -/** - * Verify that the signatures on the "403 FORBIDDEN" response from the - * exchange demonstrating customer double-spending are valid. - * - * @param dh deposit handle - * @param json json reply with the signature(s) and transaction history - * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not - */ -static int -verify_deposit_signature_forbidden (const struct TALER_EXCHANGE_DepositHandle *dh, - const json_t *json) -{ - json_t *history; - struct TALER_Amount total; - - history = json_object_get (json, - "history"); - if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (dh->coin_value.currency, - &dh->depconf.coin_pub, - history, - &total)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&total, - &total, - &dh->amount_with_fee)) - { - /* clearly not OK if our transaction would have caused - the overflow... */ - return GNUNET_OK; - } - - if (0 >= TALER_amount_cmp (&total, - &dh->coin_value)) - { - /* transaction should have still fit */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - /* everything OK, proof of double-spending was provided */ - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /deposit request. - * - * @param cls the `struct TALER_EXCHANGE_DepositHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_deposit_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_DepositHandle *dh = cls; - struct TALER_ExchangeSignatureP exchange_sig; - struct TALER_ExchangePublicKeyP exchange_pub; - struct TALER_ExchangeSignatureP *es = NULL; - struct TALER_ExchangePublicKeyP *ep = NULL; - const json_t *j = response; - - dh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - verify_deposit_signature_ok (dh, - j, - &exchange_sig, - &exchange_pub)) - { - GNUNET_break_op (0); - response_code = 0; - } - else - { - es = &exchange_sig; - ep = &exchange_pub; - } - 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_FORBIDDEN: - /* Double spending; check signatures on transaction history */ - if (GNUNET_OK != - verify_deposit_signature_forbidden (dh, - j)) - { - GNUNET_break_op (0); - response_code = 0; - } - break; - case MHD_HTTP_UNAUTHORIZED: - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - dh->cb (dh->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - es, - ep, - j); - TALER_EXCHANGE_deposit_cancel (dh); -} - - -/** - * Verify signature information about the deposit. - * - * @param dki public key information - * @param amount the amount to be deposited - * @param h_wire hash of the merchant’s account details - * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) - * @param coin_pub coin’s public key - * @param denom_pub denomination key with which the coin is signed - * @param denom_sig exchange’s unblinded signature of the coin - * @param timestamp timestamp when the deposit was finalized - * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) - * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed) - * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. - * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not - */ -static int -verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki, - const struct TALER_Amount *amount, - const struct GNUNET_HashCode *h_wire, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_DenominationSignature *denom_sig, - const struct TALER_DenominationPublicKey *denom_pub, - struct GNUNET_TIME_Absolute timestamp, - const struct TALER_MerchantPublicKeyP *merchant_pub, - struct GNUNET_TIME_Absolute refund_deadline, - const struct TALER_CoinSpendSignatureP *coin_sig) -{ - struct TALER_DepositRequestPS dr; - struct TALER_CoinPublicInfo coin_info; - - dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); - dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS)); - dr.h_contract_terms = *h_contract_terms; - dr.h_wire = *h_wire; - dr.timestamp = GNUNET_TIME_absolute_hton (timestamp); - dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); - TALER_amount_hton (&dr.amount_with_fee, - amount); - TALER_amount_hton (&dr.deposit_fee, - &dki->fee_deposit); - dr.merchant = *merchant_pub; - dr.coin_pub = *coin_pub; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT, - &dr.purpose, - &coin_sig->eddsa_signature, - &coin_pub->eddsa_pub)) - { - GNUNET_break_op (0); - TALER_LOG_WARNING ("Invalid coin signature on /deposit request!\n"); - { - TALER_LOG_DEBUG ("... amount_with_fee was %s\n", - TALER_amount2s (amount)); - TALER_LOG_DEBUG ("... deposit_fee was %s\n", - TALER_amount2s (&dki->fee_deposit)); - } - - return GNUNET_SYSERR; - } - - /* check coin signature */ - coin_info.coin_pub = *coin_pub; - coin_info.denom_pub = *denom_pub; - coin_info.denom_sig = *denom_sig; - if (GNUNET_YES != - TALER_test_coin_valid (&coin_info)) - { - GNUNET_break_op (0); - TALER_LOG_WARNING ("Invalid coin passed for /deposit\n"); - return GNUNET_SYSERR; - } - if (0 < TALER_amount_cmp (&dki->fee_deposit, - amount)) - { - GNUNET_break_op (0); - TALER_LOG_WARNING ("Deposit amount smaller than fee\n"); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Submit a deposit permission to the exchange and get the exchange's response. - * Note that while we return the response verbatim to the caller for - * further processing, we do already verify that the response is - * well-formed (i.e. that signatures included in the response are all - * valid). If the exchange's reply is not well-formed, we return an - * HTTP status code of zero to @a cb. - * - * We also verify that the @a coin_sig is valid for this deposit - * request, and that the @a ub_sig is a valid signature for @a - * coin_pub. Also, the @a exchange must be ready to operate (i.e. have - * finished processing the /keys reply). If either check fails, we do - * NOT initiate the transaction with the exchange and instead return NULL. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param amount the amount to be deposited - * @param wire_deadline date until which the merchant would like the exchange to settle the balance (advisory, the exchange cannot be - * forced to settle in the past or upon very short notice, but of course a well-behaved exchange will limit aggregation based on the advice received) - * @param wire_details the merchant’s account details, in a format supported by the exchange - * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) - * @param coin_pub coin’s public key - * @param denom_pub denomination key with which the coin is signed - * @param denom_sig exchange’s unblinded signature of the coin - * @param timestamp timestamp when the contract was finalized, must not be too far in the future - * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) - * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline - * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. - * @param cb the callback to call when a reply for this request is available - * @param cb_cls closure for the above callback - * @return a handle for this request; NULL if the inputs are invalid (i.e. - * signatures fail to verify). In this case, the callback is not called. - */ -struct TALER_EXCHANGE_DepositHandle * -TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_Amount *amount, - struct GNUNET_TIME_Absolute wire_deadline, - json_t *wire_details, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_DenominationSignature *denom_sig, - const struct TALER_DenominationPublicKey *denom_pub, - struct GNUNET_TIME_Absolute timestamp, - const struct TALER_MerchantPublicKeyP *merchant_pub, - struct GNUNET_TIME_Absolute refund_deadline, - const struct TALER_CoinSpendSignatureP *coin_sig, - TALER_EXCHANGE_DepositResultCallback cb, - void *cb_cls) -{ - const struct TALER_EXCHANGE_Keys *key_state; - const struct TALER_EXCHANGE_DenomPublicKey *dki; - struct TALER_EXCHANGE_DepositHandle *dh; - struct GNUNET_CURL_Context *ctx; - json_t *deposit_obj; - CURL *eh; - struct GNUNET_HashCode h_wire; - struct TALER_Amount amount_without_fee; - - (void) GNUNET_TIME_round_abs (&wire_deadline); - (void) GNUNET_TIME_round_abs (&refund_deadline); - GNUNET_assert (refund_deadline.abs_value_us <= wire_deadline.abs_value_us); - GNUNET_assert (GNUNET_YES == - TEAH_handle_is_ready (exchange)); - /* initialize h_wire */ - if (GNUNET_OK != - TALER_JSON_merchant_wire_signature_hash (wire_details, - &h_wire)) - { - GNUNET_break (0); - return NULL; - } - key_state = TALER_EXCHANGE_get_keys (exchange); - dki = TALER_EXCHANGE_get_denomination_key (key_state, - denom_pub); - GNUNET_assert (NULL != dki); - GNUNET_assert (GNUNET_SYSERR != - TALER_amount_subtract (&amount_without_fee, - amount, - &dki->fee_deposit)); - if (GNUNET_OK != - verify_signatures (dki, - amount, - &h_wire, - h_contract_terms, - coin_pub, - denom_sig, - denom_pub, - timestamp, - merchant_pub, - refund_deadline, - coin_sig)) - { - GNUNET_break_op (0); - return NULL; - } - - deposit_obj = json_pack ("{s:o, s:O," /* f/wire */ - " s:o, s:o," /* H_wire, h_contract_terms */ - " s:o, s:o," /* coin_pub, denom_pub */ - " s:o, s:o," /* ub_sig, timestamp */ - " s:o," /* merchant_pub */ - " s:o, s:o," /* refund_deadline, wire_deadline */ - " s:o}", /* coin_sig */ - "contribution", TALER_JSON_from_amount (amount), - "wire", wire_details, - "H_wire", GNUNET_JSON_from_data_auto (&h_wire), - "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms), - "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), - "denom_pub", GNUNET_JSON_from_rsa_public_key (denom_pub->rsa_public_key), - "ub_sig", GNUNET_JSON_from_rsa_signature (denom_sig->rsa_signature), - "timestamp", GNUNET_JSON_from_time_abs (timestamp), - "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub), - "refund_deadline", GNUNET_JSON_from_time_abs (refund_deadline), - "wire_transfer_deadline", GNUNET_JSON_from_time_abs (wire_deadline), - "coin_sig", GNUNET_JSON_from_data_auto (coin_sig) - ); - if (NULL == deposit_obj) - { - GNUNET_break (0); - return NULL; - } - - dh = GNUNET_new (struct TALER_EXCHANGE_DepositHandle); - dh->exchange = exchange; - dh->cb = cb; - dh->cb_cls = cb_cls; - dh->url = TEAH_path_to_url (exchange, "/deposit"); - dh->depconf.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)); - dh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT); - dh->depconf.h_contract_terms = *h_contract_terms; - dh->depconf.h_wire = h_wire; - dh->depconf.timestamp = GNUNET_TIME_absolute_hton (timestamp); - dh->depconf.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); - TALER_amount_hton (&dh->depconf.amount_without_fee, - &amount_without_fee); - dh->depconf.coin_pub = *coin_pub; - dh->depconf.merchant = *merchant_pub; - dh->amount_with_fee = *amount; - dh->coin_value = dki->value; - - eh = TEL_curl_easy_get (dh->url); - GNUNET_assert (NULL != (dh->json_enc = - json_dumps (deposit_obj, - JSON_COMPACT))); - json_decref (deposit_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for deposit: `%s'\n", - dh->url); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDS, - dh->json_enc)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDSIZE, - strlen (dh->json_enc))); - ctx = TEAH_handle_to_context (exchange); - dh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_deposit_finished, - dh); - return dh; -} - - -/** - * Cancel a deposit permission request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param deposit the deposit permission request handle - */ -void -TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit) -{ - if (NULL != deposit->job) - { - GNUNET_CURL_job_cancel (deposit->job); - deposit->job = NULL; - } - GNUNET_free (deposit->url); - GNUNET_free (deposit->json_enc); - GNUNET_free (deposit); -} - - -/* end of exchange_api_deposit.c */ diff --git a/src/exchange-lib/exchange_api_handle.c b/src/exchange-lib/exchange_api_handle.c @@ -1,1779 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2018 GNUnet e.V. - - 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 exchange-lib/exchange_api_handle.c - * @brief Implementation of the "handle" component of the exchange'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_exchange_service.h" -#include "taler_auditor_service.h" -#include "taler_signatures.h" -#include "exchange_api_handle.h" -#include "curl_defaults.h" -#include "backoff.h" - -/** - * Which revision of the Taler protocol is implemented - * by this library? Used to determine compatibility. - */ -#define TALER_PROTOCOL_CURRENT 2 - -/** - * How many revisions back are we compatible to? - */ -#define TALER_PROTOCOL_AGE 0 - -/** - * Current version for (local) JSON serialization of persisted - * /keys data. - */ -#define TALER_SERIALIZATION_FORMAT_VERSION 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)); - -/** - * Stages of initialization for the `struct TALER_EXCHANGE_Handle` - */ -enum ExchangeHandleState -{ - /** - * Just allocated. - */ - MHS_INIT = 0, - - /** - * Obtained the exchange's certification data and keys. - */ - MHS_CERT = 1, - - /** - * Failed to initialize (fatal). - */ - MHS_FAILED = 2 -}; - - -/** - * Data for the request to get the /keys of a exchange. - */ -struct KeysRequest; - - -/** - * Entry in list of ongoing interactions with an auditor. - */ -struct AuditorInteractionEntry -{ - /** - * DLL entry. - */ - struct AuditorInteractionEntry *next; - - /** - * DLL entry. - */ - struct AuditorInteractionEntry *prev; - - /** - * Interaction state. - */ - struct TALER_AUDITOR_DepositConfirmationHandle *dch; -}; - - -/** - * Entry in DLL of auditors used by an exchange. - */ -struct AuditorListEntry -{ - /** - * Next pointer of DLL. - */ - struct AuditorListEntry *next; - - /** - * Prev pointer of DLL. - */ - struct AuditorListEntry *prev; - - /** - * Base URL of the auditor. - */ - const char *auditor_url; - - /** - * Handle to the auditor. - */ - struct TALER_AUDITOR_Handle *ah; - - /** - * Head of DLL of interactions with this auditor. - */ - struct AuditorInteractionEntry *ai_head; - - /** - * Tail of DLL of interactions with this auditor. - */ - struct AuditorInteractionEntry *ai_tail; - - /** - * Public key of the auditor. - */ - struct TALER_AuditorPublicKeyP auditor_pub; - - /** - * Flag indicating that the auditor is available and that protocol - * version compatibility is given. - */ - int is_up; - -}; - - -/** - * Handle to the exchange - */ -struct TALER_EXCHANGE_Handle -{ - /** - * The context of this handle - */ - struct GNUNET_CURL_Context *ctx; - - /** - * The URL of the exchange (i.e. "http://exchange.taler.net/") - */ - char *url; - - /** - * Function to call with the exchange's certification data, - * NULL if this has already been done. - */ - TALER_EXCHANGE_CertificationCallback cert_cb; - - /** - * Closure to pass to @e cert_cb. - */ - void *cert_cb_cls; - - /** - * Data for the request to get the /keys of a exchange, - * NULL once we are past stage #MHS_INIT. - */ - struct KeysRequest *kr; - - /** - * Task for retrying /keys request. - */ - struct GNUNET_SCHEDULER_Task *retry_task; - - /** - * Raw key data of the exchange, only valid if - * @e handshake_complete is past stage #MHS_CERT. - */ - json_t *key_data_raw; - - /** - * Head of DLL of auditors of this exchange. - */ - struct AuditorListEntry *auditors_head; - - /** - * Tail of DLL of auditors of this exchange. - */ - struct AuditorListEntry *auditors_tail; - - /** - * Key data of the exchange, only valid if - * @e handshake_complete is past stage #MHS_CERT. - */ - struct TALER_EXCHANGE_Keys key_data; - - /** - * Retry /keys frequency. - */ - struct GNUNET_TIME_Relative retry_delay; - - /** - * When does @e key_data expire? - */ - struct GNUNET_TIME_Absolute key_data_expiration; - - /** - * Stage of the exchange's initialization routines. - */ - enum ExchangeHandleState state; - -}; - - -/* ***************** Internal /keys fetching ************* */ - -/** - * Data for the request to get the /keys of a exchange. - */ -struct KeysRequest -{ - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this handle - */ - char *url; - - /** - * Entry for this request with the `struct GNUNET_CURL_Context`. - */ - struct GNUNET_CURL_Job *job; - - /** - * Expiration time according to "Expire:" header. - * 0 if not provided by the server. - */ - struct GNUNET_TIME_Absolute expire; - -}; - - -/** - * Iterate over all available auditors for @a h, calling - * @param ah and giving it a chance to start a deposit - * confirmation interaction. - * - * @param h exchange to go over auditors for - * @param ac function to call per auditor - * @param ac_cls closure for @a ac - */ -void -TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h, - TEAH_AuditorCallback ac, - void *ac_cls) -{ - // FIXME! -} - - -/** - * Release memory occupied by a keys request. - * Note that this does not cancel the request - * itself. - * - * @param kr request to free - */ -static void -free_keys_request (struct KeysRequest *kr) -{ - GNUNET_free (kr->url); - GNUNET_free (kr); -} - - -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) - - -/** - * Parse a exchange's signing key encoded in JSON. - * - * @param[out] sign_key where to return the result - * @param check_sigs should we check signatures? - * @param[in] sign_key_obj json to parse - * @param master_key master key to use to verify signature - * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is - * invalid or the json malformed. - */ -static int -parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, - int check_sigs, - json_t *sign_key_obj, - const struct TALER_MasterPublicKeyP *master_key) -{ - struct TALER_ExchangeSigningKeyValidityPS sign_key_issue; - struct TALER_MasterSignatureP sign_key_issue_sig; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("master_sig", - &sign_key_issue_sig), - GNUNET_JSON_spec_fixed_auto ("key", - &sign_key->key), - GNUNET_JSON_spec_absolute_time ("stamp_start", - &sign_key->valid_from), - GNUNET_JSON_spec_absolute_time ("stamp_expire", - &sign_key->valid_until), - GNUNET_JSON_spec_absolute_time ("stamp_end", - &sign_key->valid_legal), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (sign_key_obj, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if (! check_sigs) - return GNUNET_OK; - sign_key_issue.signkey_pub = sign_key->key; - sign_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY); - sign_key_issue.purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS)); - sign_key_issue.master_public_key = *master_key; - sign_key_issue.start = GNUNET_TIME_absolute_hton (sign_key->valid_from); - sign_key_issue.expire = GNUNET_TIME_absolute_hton (sign_key->valid_until); - sign_key_issue.end = GNUNET_TIME_absolute_hton (sign_key->valid_legal); - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY, - &sign_key_issue.purpose, - &sign_key_issue_sig.eddsa_signature, - &master_key->eddsa_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - sign_key->master_sig = sign_key_issue_sig; - return GNUNET_OK; -} - - -/** - * Parse a exchange's denomination key encoded in JSON. - * - * @param[out] denom_key where to return the result - * @param check_sigs should we check signatures? - * @param[in] denom_key_obj json to parse - * @param master_key master key to use to verify signature - * @param hash_context where to accumulate data for signature verification - * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is - * invalid or the json malformed. - */ -static int -parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key, - int check_sigs, - json_t *denom_key_obj, - struct TALER_MasterPublicKeyP *master_key, - struct GNUNET_HashContext *hash_context) -{ - struct TALER_DenominationKeyValidityPS denom_key_issue; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("master_sig", - &denom_key->master_sig), - GNUNET_JSON_spec_absolute_time ("stamp_expire_deposit", - &denom_key->expire_deposit), - GNUNET_JSON_spec_absolute_time ("stamp_expire_withdraw", - &denom_key->withdraw_valid_until), - GNUNET_JSON_spec_absolute_time ("stamp_start", - &denom_key->valid_from), - GNUNET_JSON_spec_absolute_time ("stamp_expire_legal", - &denom_key->expire_legal), - TALER_JSON_spec_amount ("value", - &denom_key->value), - TALER_JSON_spec_amount ("fee_withdraw", - &denom_key->fee_withdraw), - TALER_JSON_spec_amount ("fee_deposit", - &denom_key->fee_deposit), - TALER_JSON_spec_amount ("fee_refresh", - &denom_key->fee_refresh), - TALER_JSON_spec_amount ("fee_refund", - &denom_key->fee_refund), - GNUNET_JSON_spec_rsa_public_key ("denom_pub", - &denom_key->key.rsa_public_key), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (denom_key_obj, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - GNUNET_CRYPTO_rsa_public_key_hash (denom_key->key.rsa_public_key, - &denom_key->h_key); - if (! check_sigs) - return GNUNET_OK; - memset (&denom_key_issue, - 0, - sizeof (denom_key_issue)); - denom_key_issue.purpose.purpose - = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY); - denom_key_issue.purpose.size - = htonl (sizeof (struct TALER_DenominationKeyValidityPS)); - denom_key_issue.master = *master_key; - denom_key_issue.denom_hash = denom_key->h_key; - denom_key_issue.start = GNUNET_TIME_absolute_hton (denom_key->valid_from); - denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (denom_key->withdraw_valid_until); - denom_key_issue.expire_deposit = GNUNET_TIME_absolute_hton (denom_key->expire_deposit); - denom_key_issue.expire_legal = GNUNET_TIME_absolute_hton (denom_key->expire_legal); - TALER_amount_hton (&denom_key_issue.value, - &denom_key->value); - TALER_amount_hton (&denom_key_issue.fee_withdraw, - &denom_key->fee_withdraw); - TALER_amount_hton (&denom_key_issue.fee_deposit, - &denom_key->fee_deposit); - TALER_amount_hton (&denom_key_issue.fee_refresh, - &denom_key->fee_refresh); - TALER_amount_hton (&denom_key_issue.fee_refund, - &denom_key->fee_refund); - EXITIF (GNUNET_SYSERR == - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY, - &denom_key_issue.purpose, - &denom_key->master_sig.eddsa_signature, - &master_key->eddsa_pub)); - GNUNET_CRYPTO_hash_context_read (hash_context, - &denom_key_issue.denom_hash, - sizeof (struct GNUNET_HashCode)); - return GNUNET_OK; - - EXITIF_exit: - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; -} - - -/** - * Parse a exchange's auditor information encoded in JSON. - * - * @param[out] auditor where to return the result - * @param check_sig should we check signatures - * @param[in] auditor_obj json to parse - * @param key_data information about denomination keys - * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is - * invalid or the json malformed. - */ -static int -parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, - int check_sigs, - json_t *auditor_obj, - const struct TALER_EXCHANGE_Keys *key_data) -{ - json_t *keys; - json_t *key; - unsigned int len; - unsigned int off; - unsigned int i; - const char *auditor_url; - struct TALER_ExchangeKeyValidityPS kv; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("auditor_pub", - &auditor->auditor_pub), - GNUNET_JSON_spec_string ("auditor_url", - &auditor_url), - GNUNET_JSON_spec_json ("denomination_keys", - &keys), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (auditor_obj, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - auditor->auditor_url = GNUNET_strdup (auditor_url); - kv.purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS); - kv.purpose.size = htonl (sizeof (struct TALER_ExchangeKeyValidityPS)); - GNUNET_CRYPTO_hash (auditor_url, - strlen (auditor_url) + 1, - &kv.auditor_url_hash); - kv.master = key_data->master_pub; - len = json_array_size (keys); - auditor->denom_keys = GNUNET_new_array (len, - struct TALER_EXCHANGE_AuditorDenominationInfo); - i = 0; - off = 0; - json_array_foreach (keys, i, key) { - struct TALER_AuditorSignatureP auditor_sig; - struct GNUNET_HashCode denom_h; - const struct TALER_EXCHANGE_DenomPublicKey *dk; - unsigned int dk_off; - struct GNUNET_JSON_Specification kspec[] = { - GNUNET_JSON_spec_fixed_auto ("auditor_sig", - &auditor_sig), - GNUNET_JSON_spec_fixed_auto ("denom_pub_h", - &denom_h), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (key, - kspec, - NULL, NULL)) - { - GNUNET_break_op (0); - continue; - } - dk = NULL; - dk_off = UINT_MAX; - for (unsigned int j=0;j<key_data->num_denom_keys;j++) - { - if (0 == memcmp (&denom_h, - &key_data->denom_keys[j].h_key, - sizeof (struct GNUNET_HashCode))) - { - dk = &key_data->denom_keys[j]; - dk_off = j; - break; - } - } - if (NULL == dk) - { - GNUNET_break_op (0); - continue; - } - if (check_sigs) - { - kv.start = GNUNET_TIME_absolute_hton (dk->valid_from); - kv.expire_withdraw = GNUNET_TIME_absolute_hton (dk->withdraw_valid_until); - kv.expire_deposit = GNUNET_TIME_absolute_hton (dk->expire_deposit); - kv.expire_legal = GNUNET_TIME_absolute_hton (dk->expire_legal); - TALER_amount_hton (&kv.value, - &dk->value); - TALER_amount_hton (&kv.fee_withdraw, - &dk->fee_withdraw); - TALER_amount_hton (&kv.fee_deposit, - &dk->fee_deposit); - TALER_amount_hton (&kv.fee_refresh, - &dk->fee_refresh); - TALER_amount_hton (&kv.fee_refund, - &dk->fee_refund); - kv.denom_hash = dk->h_key; - - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS, - &kv.purpose, - &auditor_sig.eddsa_sig, - &auditor->auditor_pub.eddsa_pub)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - } - auditor->denom_keys[off].denom_key_offset = dk_off; - auditor->denom_keys[off].auditor_sig = auditor_sig; - off++; - } - auditor->num_denom_keys = off; - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * Decode the JSON in @a resp_obj from the /keys response - * and store the data in the @a key_data. - * - * @param[in] resp_obj JSON object to parse - * @param check_sig #GNUNET_YES if we should check the signature - * @param[out] key_data where to store the results we decoded - * @param[out] where to store version compatibility data - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - * (malformed JSON) - */ -static int -decode_keys_json (const json_t *resp_obj, - int check_sig, - struct TALER_EXCHANGE_Keys *key_data, - enum TALER_EXCHANGE_VersionCompatibility *vc) -{ - struct TALER_ExchangeSignatureP sig; - struct GNUNET_HashContext *hash_context; - struct TALER_ExchangePublicKeyP pub; - unsigned int age; - unsigned int revision; - unsigned int current; - struct GNUNET_JSON_Specification mspec[] = { - GNUNET_JSON_spec_fixed_auto ("eddsa_sig", - &sig), - GNUNET_JSON_spec_fixed_auto ("eddsa_pub", - &pub), - /* sig and pub must be first, as we skip those if - check_sig is false! */ - GNUNET_JSON_spec_fixed_auto ("master_public_key", - &key_data->master_pub), - GNUNET_JSON_spec_absolute_time ("list_issue_date", - &key_data->list_issue_date), - GNUNET_JSON_spec_relative_time - ("reserve_closing_delay", - &key_data->reserve_closing_delay), - GNUNET_JSON_spec_end() - }; - - if (JSON_OBJECT != json_typeof (resp_obj)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* check the version */ - { - const char *ver; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("version", - &ver), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (resp_obj, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (3 != sscanf (ver, - "%u:%u:%u", - &current, - &revision, - &age)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - *vc = TALER_EXCHANGE_VC_MATCH; - if (TALER_PROTOCOL_CURRENT < current) - { - *vc |= TALER_EXCHANGE_VC_NEWER; - if (TALER_PROTOCOL_CURRENT < current - age) - *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; - } - if (TALER_PROTOCOL_CURRENT > current) - { - *vc |= TALER_EXCHANGE_VC_OLDER; - if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current) - *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; - } - key_data->version = GNUNET_strdup (ver); - } - - hash_context = NULL; - EXITIF (GNUNET_OK != - GNUNET_JSON_parse (resp_obj, - (check_sig) ? mspec : &mspec[2], - NULL, NULL)); - - /* parse the master public key and issue date of the response */ - if (check_sig) - hash_context = GNUNET_CRYPTO_hash_context_start (); - - /* parse the signing keys */ - { - json_t *sign_keys_array; - json_t *sign_key_obj; - unsigned int index; - - EXITIF (NULL == (sign_keys_array = - json_object_get (resp_obj, - "signkeys"))); - EXITIF (JSON_ARRAY != json_typeof (sign_keys_array)); - EXITIF (0 == (key_data->num_sign_keys = - json_array_size (sign_keys_array))); - key_data->sign_keys - = GNUNET_new_array (key_data->num_sign_keys, - struct TALER_EXCHANGE_SigningPublicKey); - index = 0; - json_array_foreach (sign_keys_array, index, sign_key_obj) { - EXITIF (GNUNET_SYSERR == - parse_json_signkey (&key_data->sign_keys[index], - check_sig, - sign_key_obj, - &key_data->master_pub)); - } - } - - /* parse the denomination keys, merging with the - possibly EXISTING array as required (/keys cherry picking) */ - { - json_t *denom_keys_array; - json_t *denom_key_obj; - unsigned int index; - - EXITIF (NULL == (denom_keys_array = - json_object_get (resp_obj, - "denoms"))); - EXITIF (JSON_ARRAY != json_typeof (denom_keys_array)); - - index = 0; - json_array_foreach (denom_keys_array, index, denom_key_obj) { - struct TALER_EXCHANGE_DenomPublicKey dk; - bool found = false; - - EXITIF (GNUNET_SYSERR == - parse_json_denomkey (&dk, - check_sig, - denom_key_obj, - &key_data->master_pub, - hash_context)); - for (unsigned int j=0;j<key_data->num_denom_keys;j++) - { - if (0 == memcmp (&dk, - &key_data->denom_keys[j], - sizeof (dk))) - { - found = true; - break; - } - } - if (found) - { - /* 0:0:0 did not support /keys cherry picking */ - GNUNET_break_op (0 == current); - continue; - } - if (key_data->denom_keys_size == key_data->num_denom_keys) - GNUNET_array_grow (key_data->denom_keys, - key_data->denom_keys_size, - key_data->denom_keys_size * 2 + 2); - key_data->denom_keys[key_data->num_denom_keys++] = dk; - - /* Update "last_denom_issue_date" */ - TALER_LOG_DEBUG ("Crawling DK 'valid_from': %s\n", - GNUNET_STRINGS_absolute_time_to_string (dk.valid_from)); - key_data->last_denom_issue_date - = GNUNET_TIME_absolute_max (key_data->last_denom_issue_date, - dk.valid_from); - }; - } - /* parse the auditor information */ - { - json_t *auditors_array; - json_t *auditor_info; - unsigned int index; - - EXITIF (NULL == (auditors_array = - json_object_get (resp_obj, - "auditors"))); - EXITIF (JSON_ARRAY != json_typeof (auditors_array)); - - /* Merge with the existing auditor information we have (/keys cherry picking) */ - index = 0; - json_array_foreach (auditors_array, index, auditor_info) { - struct TALER_EXCHANGE_AuditorInformation ai; - bool found = false; - - memset (&ai, - 0, - sizeof (ai)); - EXITIF (GNUNET_SYSERR == - parse_json_auditor (&ai, - check_sig, - auditor_info, - key_data)); - for (unsigned int j=0;j<key_data->num_auditors;j++) - { - struct TALER_EXCHANGE_AuditorInformation *aix = &key_data->auditors[j]; - - if (0 == memcmp (&ai.auditor_pub, - &aix->auditor_pub, - sizeof (struct TALER_AuditorPublicKeyP))) - { - found = true; - /* Merge denomination key signatures of downloaded /keys into existing - auditor information 'aix'. */ - GNUNET_array_grow (aix->denom_keys, - aix->num_denom_keys, - aix->num_denom_keys + ai.num_denom_keys); - memcpy (&aix->denom_keys[aix->num_denom_keys - ai.num_denom_keys], - ai.denom_keys, - ai.num_denom_keys * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); - break; - } - } - if (found) - continue; /* we are done */ - if (key_data->auditors_size == key_data->num_auditors) - GNUNET_array_grow (key_data->auditors, - key_data->auditors_size, - key_data->auditors_size * 2 + 2); - key_data->auditors[key_data->num_auditors++] = ai; - }; - } - - if (check_sig) - { - struct TALER_ExchangeKeySetPS ks; - - /* Validate signature... */ - ks.purpose.size = htonl (sizeof (ks)); - ks.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET); - ks.list_issue_date = GNUNET_TIME_absolute_hton (key_data->list_issue_date); - GNUNET_CRYPTO_hash_context_finish (hash_context, - &ks.hc); - hash_context = NULL; - EXITIF (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_data, - &pub)); - EXITIF (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_KEY_SET, - &ks.purpose, - &sig.eddsa_signature, - &pub.eddsa_pub)); - } - return GNUNET_OK; - EXITIF_exit: - - if (NULL != hash_context) - GNUNET_CRYPTO_hash_context_abort (hash_context); - return GNUNET_SYSERR; -} - - -/** - * Free key data object. - * - * @param key_data data to free (pointer itself excluded) - */ -static void -free_key_data (struct TALER_EXCHANGE_Keys *key_data) -{ - GNUNET_array_grow (key_data->sign_keys, - key_data->num_sign_keys, - 0); - for (unsigned int i=0;i<key_data->num_denom_keys;i++) - GNUNET_CRYPTO_rsa_public_key_free (key_data->denom_keys[i].key.rsa_public_key); - - GNUNET_array_grow (key_data->denom_keys, - key_data->denom_keys_size, - 0); - for (unsigned int i=0;i<key_data->num_auditors;i++) - { - GNUNET_array_grow (key_data->auditors[i].denom_keys, - key_data->auditors[i].num_denom_keys, - 0); - GNUNET_free (key_data->auditors[i].auditor_url); - } - GNUNET_array_grow (key_data->auditors, - key_data->auditors_size, - 0); - GNUNET_free_non_null (key_data->version); - key_data->version = NULL; -} - - -/** - * Initiate download of /keys from the exchange. - * - * @param cls exchange where to download /keys from - */ -static void -request_keys (void *cls); - - -/** - * Check if our current response for /keys is valid, and if - * not trigger download. - * - * @param exchange exchange to check keys for - * @param force_download #GNUNET_YES to force download even if /keys is still valid - * @return until when the response is current, 0 if we are re-downloading - */ -struct GNUNET_TIME_Absolute -TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange, - int force_download) -{ - if (NULL != exchange->kr) - return GNUNET_TIME_UNIT_ZERO_ABS; - if ( (GNUNET_NO == force_download) && - (0 < GNUNET_TIME_absolute_get_remaining (exchange->key_data_expiration).rel_value_us) ) - return exchange->key_data_expiration; - if (NULL == exchange->retry_task) - exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys, - exchange); - return GNUNET_TIME_UNIT_ZERO_ABS; -} - - -/** - * Callback used when downloading the reply to a /keys request - * is complete. - * - * @param cls the `struct KeysRequest` - * @param response_code HTTP response code, 0 on error - * @param resp_obj parsed JSON result, NULL on error - */ -static void -keys_completed_cb (void *cls, - long response_code, - const void *resp_obj) -{ - struct KeysRequest *kr = cls; - struct TALER_EXCHANGE_Handle *exchange = kr->exchange; - struct TALER_EXCHANGE_Keys kd; - struct TALER_EXCHANGE_Keys kd_old; - enum TALER_EXCHANGE_VersionCompatibility vc; - const json_t *j = resp_obj; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Received keys from URL `%s' with status %ld.\n", - kr->url, - response_code); - kd_old = exchange->key_data; - memset (&kd, - 0, - sizeof (struct TALER_EXCHANGE_Keys)); - vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR; - switch (response_code) - { - case 0: - free_keys_request (kr); - exchange->kr = NULL; - GNUNET_assert (NULL == exchange->retry_task); - exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay); - exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay, - &request_keys, - exchange); - return; - case MHD_HTTP_OK: - if (NULL == j) - { - response_code = 0; - break; - } - /* We keep the denomination keys and auditor signatures from the - previous iteration (/keys cherry picking) */ - kd.num_denom_keys = kd_old.num_denom_keys; - kd.last_denom_issue_date = kd_old.last_denom_issue_date; - GNUNET_array_grow (kd.denom_keys, - kd.denom_keys_size, - kd.num_denom_keys); - - /* First make a shallow copy, we then need another pass for the RSA key... */ - memcpy (kd.denom_keys, - kd_old.denom_keys, - kd_old.num_denom_keys * sizeof (struct TALER_EXCHANGE_DenomPublicKey)); - for (unsigned int i=0;i<kd_old.num_denom_keys;i++) - kd.denom_keys[i].key.rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_dup (kd_old.denom_keys[i].key.rsa_public_key); - - kd.num_auditors = kd_old.num_auditors; - kd.auditors = GNUNET_new_array (kd.num_auditors, - struct TALER_EXCHANGE_AuditorInformation); - /* Now the necessary deep copy... */ - for (unsigned int i=0;i<kd_old.num_auditors;i++) - { - const struct TALER_EXCHANGE_AuditorInformation *aold = &kd_old.auditors[i]; - struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i]; - - anew->auditor_pub = aold->auditor_pub; - anew->auditor_url = GNUNET_strdup (aold->auditor_url); - GNUNET_array_grow (anew->denom_keys, - anew->num_denom_keys, - aold->num_denom_keys); - memcpy (anew->denom_keys, - aold->denom_keys, - aold->num_denom_keys * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); - } - - if (GNUNET_OK != - decode_keys_json (j, - GNUNET_YES, - &kd, - &vc)) - { - TALER_LOG_ERROR ("Could not decode /keys response\n"); - response_code = 0; - for (unsigned int i=0;i<kd.num_auditors;i++) - { - struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i]; - - GNUNET_array_grow (anew->denom_keys, - anew->num_denom_keys, - 0); - GNUNET_free (anew->auditor_url); - } - GNUNET_free (kd.auditors); - kd.auditors = NULL; - kd.num_auditors = 0; - for (unsigned int i=0;i<kd_old.num_denom_keys;i++) - GNUNET_CRYPTO_rsa_public_key_free (kd.denom_keys[i].key.rsa_public_key); - GNUNET_array_grow (kd.denom_keys, - kd.denom_keys_size, - 0); - kd.num_denom_keys = 0; - break; - } - json_decref (exchange->key_data_raw); - exchange->key_data_raw = json_deep_copy (j); - exchange->retry_delay = GNUNET_TIME_UNIT_ZERO; - break; - default: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - break; - } - exchange->key_data = kd; - TALER_LOG_DEBUG ("Last DK issue date update to: %s\n", - GNUNET_STRINGS_absolute_time_to_string - (exchange->key_data.last_denom_issue_date)); - - - if (MHD_HTTP_OK != response_code) - { - exchange->kr = NULL; - free_keys_request (kr); - exchange->state = MHS_FAILED; - if (NULL != exchange->key_data_raw) - { - json_decref (exchange->key_data_raw); - exchange->key_data_raw = NULL; - } - free_key_data (&kd_old); - /* notify application that we failed */ - exchange->cert_cb (exchange->cert_cb_cls, - NULL, - vc); - return; - } - - exchange->kr = NULL; - exchange->key_data_expiration = kr->expire; - free_keys_request (kr); - exchange->state = MHS_CERT; - /* notify application about the key information */ - exchange->cert_cb (exchange->cert_cb_cls, - &exchange->key_data, - vc); - free_key_data (&kd_old); -} - - -/* ********************* library internal API ********* */ - - -/** - * Get the context of a exchange. - * - * @param h the exchange handle to query - * @return ctx context to execute jobs in - */ -struct GNUNET_CURL_Context * -TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h) -{ - return h->ctx; -} - - -/** - * Check if the handle is ready to process requests. - * - * @param h the exchange handle to query - * @return #GNUNET_YES if we are ready, #GNUNET_NO if not - */ -int -TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h) -{ - return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO; -} - - -/** - * Obtain the URL to use for an API request. - * - * @param h handle for the exchange - * @param path Taler API path (i.e. "/reserve/withdraw") - * @return the full URL to use with cURL - */ -char * -TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h, - const char *path) -{ - return TEAH_path_to_url2 (h->url, - path); -} - - -/** - * Obtain the URL to use for an API request. - * - * @param base_url base URL of the exchange (i.e. "http://exchange/") - * @param path Taler API path (i.e. "/reserve/withdraw") - * @return the full URL to use with cURL - */ -char * -TEAH_path_to_url2 (const char *base_url, - const char *path) -{ - char *url; - - if ( ('/' == path[0]) && - (0 < strlen (base_url)) && - ('/' == base_url[strlen (base_url) - 1]) ) - path++; /* avoid generating URL with "//" from concat */ - GNUNET_asprintf (&url, - "%s%s", - base_url, - path); - return url; -} - - -/** - * Parse HTTP timestamp. - * - * @param date header to parse header - * @param at where to write the result - * @return #GNUNET_OK on success - */ -static int -parse_date_string (const char *date, - struct GNUNET_TIME_Absolute *at) -{ - struct tm now; - time_t t; - const char *end; - - memset (&now, - 0, - sizeof (now)); - end = strptime (date, - "%a, %d %b %Y %H:%M:%S %Z", /* RFC-1123 standard spec */ - &now); - if ( (NULL == end) || - ( (*end != '\n') && - (*end != '\r') ) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - t = mktime (&now); - if (((time_t) -1) == t) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, - "mktime"); - return GNUNET_SYSERR; - } - if (t < 0) - t = 0; /* can happen due to timezone issues if date was 1.1.1970 */ - at->abs_value_us = 1000LL * 1000LL * t; - return GNUNET_OK; -} - - -/** - * Function called for each header in the HTTP /keys response. - * Finds the "Expire:" header and parses it, storing the result - * in the "expire" field fo the keys request. - * - * @param buffer header data received - * @param size size of an item in @a buffer - * @param nitems number of items in @a buffer - * @param userdata the `struct KeysRequest` - * @return `size * nitems` on success (everything else aborts) - */ -static size_t -header_cb (char *buffer, - size_t size, - size_t nitems, - void *userdata) -{ - struct KeysRequest *kr = userdata; - size_t total = size * nitems; - char *val; - - if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": ")) - return total; - if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ", - buffer, - strlen (MHD_HTTP_HEADER_EXPIRES ": "))) - return total; - val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")], - total - strlen (MHD_HTTP_HEADER_EXPIRES ": ")); - if (GNUNET_OK != - parse_date_string (val, - &kr->expire)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s-header `%s'\n", - MHD_HTTP_HEADER_EXPIRES, - val); - kr->expire = GNUNET_TIME_UNIT_ZERO_ABS; - } - GNUNET_free (val); - return total; -} - - -/* ********************* public API ******************* */ - - -/** - * Deserialize the key data and use it to bootstrap @a exchange to - * more efficiently recover the state. Errors in @a data must be - * tolerated (i.e. by re-downloading instead). - * - * @param exchange which exchange's key and wire data should be deserialized - * @return data the data to deserialize - */ -static void -deserialize_data (struct TALER_EXCHANGE_Handle *exchange, - const json_t *data) -{ - enum TALER_EXCHANGE_VersionCompatibility vc; - json_t *keys; - const char *url; - uint32_t version; - struct GNUNET_TIME_Absolute expire; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint32 ("version", - &version), - GNUNET_JSON_spec_json ("keys", - &keys), - GNUNET_JSON_spec_string ("url", - &url), - GNUNET_JSON_spec_absolute_time ("expire", - &expire), - GNUNET_JSON_spec_end() - }; - struct TALER_EXCHANGE_Keys key_data; - - if (NULL == data) - return; - if (GNUNET_OK != - GNUNET_JSON_parse (data, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return; - } - if (0 != version) - return; /* unsupported version */ - if (0 != strcmp (url, - exchange->url)) - { - GNUNET_break (0); - return; - } - memset (&key_data, - 0, - sizeof (struct TALER_EXCHANGE_Keys)); - if (GNUNET_OK != - decode_keys_json (keys, - GNUNET_NO, - &key_data, - &vc)) - { - GNUNET_break (0); - return; - } - /* decode successful, initialize with the result */ - GNUNET_assert (NULL == exchange->key_data_raw); - exchange->key_data_raw = json_deep_copy (keys); - exchange->key_data = key_data; - exchange->key_data_expiration = expire; - exchange->state = MHS_CERT; - /* notify application about the key information */ - exchange->cert_cb (exchange->cert_cb_cls, - &exchange->key_data, - vc); -} - - -/** - * Serialize the latest key data from @a exchange to be persisted on - * disk (to be used with #TALER_EXCHANGE_OPTION_DATA to more - * efficiently recover the state). - * - * @param exchange which exchange's key and wire data should be - * serialized - * @return NULL on error (i.e. no current data available); - * otherwise JSON object owned by the caller - */ -json_t * -TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange) -{ - const struct TALER_EXCHANGE_Keys *kd = &exchange->key_data; - struct GNUNET_TIME_Absolute now; - json_t *keys; - json_t *signkeys; - json_t *denoms; - json_t *auditors; - - now = GNUNET_TIME_absolute_get (); - signkeys = json_array (); - for (unsigned int i=0;i<kd->num_sign_keys;i++) - { - const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i]; - json_t *signkey; - - if (now.abs_value_us > sk->valid_until.abs_value_us) - continue; /* skip keys that have expired */ - signkey = json_pack ("{s:o, s:o, s:o, s:o, s:o}", - "key", - GNUNET_JSON_from_data_auto (&sk->key), - "master_sig", - GNUNET_JSON_from_data_auto (&sk->master_sig), - "stamp_start", - GNUNET_JSON_from_time_abs (sk->valid_from), - "stamp_expire", - GNUNET_JSON_from_time_abs (sk->valid_until), - "stamp_end", - GNUNET_JSON_from_time_abs (sk->valid_legal)); - if (NULL == signkey) - { - GNUNET_break (0); - continue; - } - json_array_append_new (signkeys, - signkey); - } - denoms = json_array (); - for (unsigned int i=0;i<kd->num_denom_keys;i++) - { - const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i]; - json_t *denom; - - if (now.abs_value_us > dk->expire_deposit.abs_value_us) - continue; /* skip keys that have expired */ - denom = json_pack ("{s:o, s:o, s:o, s:o, s:o " - ",s:o, s:o, s:o, s:o, s:o " - ",s:o}", - "stamp_expire_deposit", - GNUNET_JSON_from_time_abs (dk->expire_deposit), - "stamp_expire_withdraw", - GNUNET_JSON_from_time_abs (dk->withdraw_valid_until), - "stamp_start", - GNUNET_JSON_from_time_abs (dk->valid_from), - "stamp_expire_legal", - GNUNET_JSON_from_time_abs (dk->expire_legal), - "value", - TALER_JSON_from_amount (&dk->value), - "fee_withdraw", - /* #6 */ - TALER_JSON_from_amount (&dk->fee_withdraw), - "fee_deposit", - TALER_JSON_from_amount (&dk->fee_deposit), - "fee_refresh", - TALER_JSON_from_amount (&dk->fee_refresh), - "fee_refund", - TALER_JSON_from_amount (&dk->fee_refund), - "master_sig", - GNUNET_JSON_from_data_auto (&dk->master_sig), - /* #10 */ - "denom_pub", - GNUNET_JSON_from_rsa_public_key (dk->key.rsa_public_key)); - if (NULL == denom) - { - GNUNET_break (0); - continue; - } - json_array_append_new (denoms, - denom); - } - auditors = json_array (); - for (unsigned int i=0;i<kd->num_auditors;i++) - { - const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i]; - json_t *a; - json_t *adenoms; - - adenoms = json_array (); - for (unsigned int j=0;j<ai->num_denom_keys;j++) - { - const struct TALER_EXCHANGE_AuditorDenominationInfo *adi = &ai->denom_keys[j]; - const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[adi->denom_key_offset]; - json_t *k; - - if (now.abs_value_us > dk->expire_deposit.abs_value_us) - continue; /* skip auditor signatures for denomination keys that have expired */ - GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys); - k = json_pack ("{s:o, s:o}", - "denom_pub_h", - GNUNET_JSON_from_data_auto (&dk->h_key), - "auditor_sig", - GNUNET_JSON_from_data_auto (&adi->auditor_sig)); - if (NULL == k) - { - GNUNET_break (0); - continue; - } - json_array_append_new (adenoms, - k); - } - - a = json_pack ("{s:o, s:s, s:o}", - "auditor_pub", - GNUNET_JSON_from_data_auto (&ai->auditor_pub), - "auditor_url", - ai->auditor_url, - "denomination_keys", - adenoms); - if (NULL == a) - { - GNUNET_break (0); - continue; - } - json_array_append_new (auditors, - a); - } - keys = json_pack ("{s:s, s:o, s:o, s:o, s:o" - ",s:o, s:o}", - /* 1 */ - "version", - kd->version, - "master_public_key", - GNUNET_JSON_from_data_auto (&kd->master_pub), - "reserve_closing_delay", - GNUNET_JSON_from_time_rel (kd->reserve_closing_delay), - "list_issue_date", - GNUNET_JSON_from_time_abs (kd->list_issue_date), - "signkeys", - signkeys, - /* #6 */ - "denoms", - denoms, - "auditors", - auditors); - if (NULL == keys) - { - GNUNET_break (0); - return NULL; - } - return json_pack ("{s:I, s:o, s:s, s:o}", - "version", - (json_int_t) TALER_SERIALIZATION_FORMAT_VERSION, - "expire", - GNUNET_JSON_from_time_abs (exchange->key_data_expiration), - "url", - exchange->url, - "keys", - keys); -} - - -/** - * Initialise a connection to the exchange. Will connect to the - * exchange and obtain information about the exchange's master - * public key and the exchange's auditor. - * The respective information will be passed to the @a cert_cb - * once available, and all future interactions with the exchange - * will be checked to be signed (where appropriate) by the - * respective master key. - * - * @param ctx the context - * @param url HTTP base URL for the exchange - * @param cert_cb function to call with the exchange's - * certification information - * @param cert_cb_cls closure for @a cert_cb - * @param ... list of additional arguments, - * terminated by #TALER_EXCHANGE_OPTION_END. - * @return the exchange handle; NULL upon error - */ -struct TALER_EXCHANGE_Handle * -TALER_EXCHANGE_connect - (struct GNUNET_CURL_Context *ctx, - const char *url, - TALER_EXCHANGE_CertificationCallback cert_cb, - void *cert_cb_cls, - ...) -{ - struct TALER_EXCHANGE_Handle *exchange; - va_list ap; - enum TALER_EXCHANGE_Option opt; - - exchange = GNUNET_new (struct TALER_EXCHANGE_Handle); - exchange->ctx = ctx; - exchange->url = GNUNET_strdup (url); - exchange->cert_cb = cert_cb; - exchange->cert_cb_cls = cert_cb_cls; - exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys, - exchange); - va_start (ap, cert_cb_cls); - while (TALER_EXCHANGE_OPTION_END != - (opt = va_arg (ap, int))) - { - switch (opt) { - case TALER_EXCHANGE_OPTION_END: - GNUNET_assert (0); - break; - case TALER_EXCHANGE_OPTION_DATA: - { - const json_t *data = va_arg (ap, const json_t *); - - deserialize_data (exchange, - data); - break; - } - default: - GNUNET_assert (0); - break; - } - } - va_end (ap); - return exchange; -} - - -/** - * Initiate download of /keys from the exchange. - * - * @param cls exchange where to download /keys from - */ -static void -request_keys (void *cls) -{ - struct TALER_EXCHANGE_Handle *exchange = cls; - struct KeysRequest *kr; - CURL *eh; - - exchange->retry_task = NULL; - GNUNET_assert (NULL == exchange->kr); - kr = GNUNET_new (struct KeysRequest); - kr->exchange = exchange; - if (GNUNET_YES == - TEAH_handle_is_ready (exchange)) - { - char *arg; - - TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n", - GNUNET_STRINGS_absolute_time_to_string (exchange->key_data.last_denom_issue_date)); - GNUNET_asprintf (&arg, - "/keys?last_issue_date=%llu", - (unsigned long long) exchange->key_data.last_denom_issue_date.abs_value_us / 1000000LLU); - kr->url = TEAH_path_to_url (exchange, - arg); - GNUNET_free (arg); - } - else - { - kr->url = TEAH_path_to_url (exchange, - "/keys"); - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting keys with URL `%s'.\n", - kr->url); - eh = TEL_curl_easy_get (kr->url); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_VERBOSE, - 0)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_TIMEOUT, - (long) 300)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_HEADERFUNCTION, - &header_cb)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_HEADERDATA, - kr)); - kr->job = GNUNET_CURL_job_add (exchange->ctx, - eh, - GNUNET_YES, - &keys_completed_cb, - kr); - exchange->kr = kr; -} - - -/** - * Disconnect from the exchange - * - * @param exchange the exchange handle - */ -void -TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange) -{ - if (NULL != exchange->kr) - { - GNUNET_CURL_job_cancel (exchange->kr->job); - free_keys_request (exchange->kr); - exchange->kr = NULL; - } - free_key_data (&exchange->key_data); - if (NULL != exchange->key_data_raw) - { - json_decref (exchange->key_data_raw); - exchange->key_data_raw = NULL; - } - if (NULL != exchange->retry_task) - { - GNUNET_SCHEDULER_cancel (exchange->retry_task); - exchange->retry_task = NULL; - } - GNUNET_free (exchange->url); - GNUNET_free (exchange); -} - - -/** - * Lookup the given @a pub in @a keys. - * - * @param keys the exchange's key set - * @param pub claimed current online signing key for the exchange - * @return NULL if @a pub was not found - */ -const struct TALER_EXCHANGE_SigningPublicKey * -TALER_EXCHANGE_get_signing_key_details (const struct TALER_EXCHANGE_Keys *keys, - const struct TALER_ExchangePublicKeyP *pub) -{ - for (unsigned int i=0;i<keys->num_sign_keys;i++) - { - struct TALER_EXCHANGE_SigningPublicKey *spk = &keys->sign_keys[i]; - - if (0 == memcmp (pub, - &spk->key, - sizeof (struct TALER_ExchangePublicKeyP))) - return spk; - } - return NULL; -} - - -/** - * Test if the given @a pub is a the current signing key from the exchange - * according to @a keys. - * - * @param keys the exchange's key set - * @param pub claimed current online signing key for the exchange - * @return #GNUNET_OK if @a pub is (according to /keys) a current signing key - */ -int -TALER_EXCHANGE_test_signing_key (const struct TALER_EXCHANGE_Keys *keys, - const struct TALER_ExchangePublicKeyP *pub) -{ - struct GNUNET_TIME_Absolute now; - - /* we will check using a tolerance of 1h for the time */ - now = GNUNET_TIME_absolute_get (); - for (unsigned int i=0;i<keys->num_sign_keys;i++) - if ( (keys->sign_keys[i].valid_from.abs_value_us <= now.abs_value_us + 60 * 60 * 1000LL * 1000LL) && - (keys->sign_keys[i].valid_until.abs_value_us > now.abs_value_us - 60 * 60 * 1000LL * 1000LL) && - (0 == memcmp (pub, - &keys->sign_keys[i].key, - sizeof (struct TALER_ExchangePublicKeyP))) ) - return GNUNET_OK; - return GNUNET_SYSERR; -} - - -/** - * Get exchange's base URL. - * - * @param exchange exchange handle. - * @return the base URL from the handle. - */ -const char * -TALER_EXCHANGE_get_base_url (const struct TALER_EXCHANGE_Handle *exchange) -{ - return exchange->url; -} - - -/** - * Obtain the denomination key details from the exchange. - * - * @param keys the exchange's key set - * @param pk public key of the denomination to lookup - * @return details about the given denomination key, NULL if the key is - * not found - */ -const struct TALER_EXCHANGE_DenomPublicKey * -TALER_EXCHANGE_get_denomination_key (const struct TALER_EXCHANGE_Keys *keys, - const struct TALER_DenominationPublicKey *pk) -{ - for (unsigned int i=0;i<keys->num_denom_keys;i++) - if (0 == GNUNET_CRYPTO_rsa_public_key_cmp (pk->rsa_public_key, - keys->denom_keys[i].key.rsa_public_key)) - return &keys->denom_keys[i]; - return NULL; -} - - -/** - * Obtain the denomination key details from the exchange. - * - * @param keys the exchange's key set - * @param hc hash of the public key of the denomination to lookup - * @return details about the given denomination key - */ -const struct TALER_EXCHANGE_DenomPublicKey * -TALER_EXCHANGE_get_denomination_key_by_hash (const struct TALER_EXCHANGE_Keys *keys, - const struct GNUNET_HashCode *hc) -{ - for (unsigned int i=0;i<keys->num_denom_keys;i++) - if (0 == memcmp (hc, - &keys->denom_keys[i].h_key, - sizeof (struct GNUNET_HashCode))) - return &keys->denom_keys[i]; - return NULL; -} - - -/** - * Obtain the keys from the exchange. - * - * @param exchange the exchange handle - * @return the exchange's key set - */ -const struct TALER_EXCHANGE_Keys * -TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange) -{ - (void) TALER_EXCHANGE_check_keys_current (exchange, - GNUNET_NO); - return &exchange->key_data; -} - - -/** - * Obtain the keys from the exchange in the - * raw JSON format - * - * @param exchange the exchange handle - * @return the exchange's keys in raw JSON - */ -json_t * -TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange) -{ - (void) TALER_EXCHANGE_check_keys_current (exchange, - GNUNET_NO); - return json_deep_copy (exchange->key_data_raw); -} - - -/* end of exchange_api_handle.c */ diff --git a/src/exchange-lib/exchange_api_payback.c b/src/exchange-lib/exchange_api_payback.c @@ -1,374 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2017 GNUnet e.V. and Inria - - 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 exchange-lib/exchange_api_payback.c - * @brief Implementation of the /payback 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 "curl_defaults.h" - - -/** - * @brief A Payback Handle - */ -struct TALER_EXCHANGE_PaybackHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * JSON encoding of the request to POST. - */ - char *json_enc; - - /** - * Denomination key of the coin. - */ - const struct TALER_EXCHANGE_DenomPublicKey *pk; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_PaybackResultCallback 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; - -}; - - -/** - * Verify that the signature on the "200 OK" response - * from the exchange is valid. If it is, call the - * callback. - * - * @param ph payback 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 int -verify_payback_signature_ok (const struct TALER_EXCHANGE_PaybackHandle *ph, - const json_t *json) -{ - struct TALER_PaybackConfirmationPS pc; - struct TALER_ExchangePublicKeyP exchange_pub; - struct TALER_ExchangeSignatureP exchange_sig; - struct TALER_Amount amount; - struct GNUNET_TIME_Absolute timestamp; - 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), - TALER_JSON_spec_amount ("amount", &amount), - GNUNET_JSON_spec_absolute_time ("timestamp", &timestamp), - GNUNET_JSON_spec_fixed_auto ("reserve_pub", &pc.reserve_pub), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - key_state = TALER_EXCHANGE_get_keys (ph->exchange); - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_state, - &exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK); - pc.purpose.size = htonl (sizeof (pc)); - pc.timestamp = GNUNET_TIME_absolute_hton (timestamp); - TALER_amount_hton (&pc.payback_amount, - &amount); - pc.coin_pub = ph->coin_pub; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK, - &pc.purpose, - &exchange_sig.eddsa_signature, - &exchange_pub.eddsa_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - ph->cb (ph->cb_cls, - MHD_HTTP_OK, - TALER_EC_NONE, - &amount, - timestamp, - &pc.reserve_pub, - json); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /payback request. - * - * @param cls the `struct TALER_EXCHANGE_PaybackHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_payback_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_PaybackHandle *ph = cls; - const json_t *j = response; - - ph->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - verify_payback_signature_ok (ph, - j)) - { - GNUNET_break_op (0); - response_code = 0; - } - TALER_EXCHANGE_payback_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 */ - break; - case MHD_HTTP_FORBIDDEN: - { - /* Insufficient funds, proof attached */ - json_t *history; - struct TALER_Amount total; - const struct TALER_EXCHANGE_DenomPublicKey *dki; - - dki = ph->pk; - history = json_object_get (j, - "history"); - if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (dki->fee_deposit.currency, - &ph->coin_pub, - history, - &total)) - { - GNUNET_break_op (0); - response_code = 0; - } - ph->cb (ph->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - &total, - GNUNET_TIME_UNIT_FOREVER_ABS, - NULL, - j); - TALER_EXCHANGE_payback_cancel (ph); - return; - } - case MHD_HTTP_UNAUTHORIZED: - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_GONE: - /* Kind of normal: the money was already sent to the merchant - (it was too late for the refund). */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - ph->cb (ph->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - NULL, - GNUNET_TIME_UNIT_FOREVER_ABS, - NULL, - j); - TALER_EXCHANGE_payback_cancel (ph); -} - - -/** - * Ask the exchange to pay back a coin due to the exchange triggering - * the emergency payback protocol for a given denomination. The value - * of the coin will be refunded to the original customer (without fees). - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param pk kind of coin to pay back - * @param denom_sig signature over the coin by the exchange using @a pk - * @param ps secret internals of the original planchet - * @param payback_cb the callback to call when the final result for this request is available - * @param payback_cb_cls closure for @a payback_cb - * @return NULL - * if the inputs are invalid (i.e. denomination key not with this exchange). - * In this case, the callback is not called. - */ -struct TALER_EXCHANGE_PaybackHandle * -TALER_EXCHANGE_payback (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_EXCHANGE_DenomPublicKey *pk, - const struct TALER_DenominationSignature *denom_sig, - const struct TALER_PlanchetSecretsP *ps, - TALER_EXCHANGE_PaybackResultCallback payback_cb, - void *payback_cb_cls) -{ - struct TALER_EXCHANGE_PaybackHandle *ph; - struct GNUNET_CURL_Context *ctx; - struct TALER_PaybackRequestPS pr; - struct TALER_CoinSpendSignatureP coin_sig; - json_t *payback_obj; - CURL *eh; - - GNUNET_assert (GNUNET_YES == - TEAH_handle_is_ready (exchange)); - pr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_PAYBACK); - pr.purpose.size = htonl (sizeof (struct TALER_PaybackRequestPS)); - GNUNET_CRYPTO_eddsa_key_get_public (&ps->coin_priv.eddsa_priv, - &pr.coin_pub.eddsa_pub); - pr.h_denom_pub = pk->h_key; - pr.coin_blind = ps->blinding_key; - GNUNET_assert (GNUNET_OK == - GNUNET_CRYPTO_eddsa_sign (&ps->coin_priv.eddsa_priv, - &pr.purpose, - &coin_sig.eddsa_signature)); - - payback_obj = json_pack ("{s:o, s:o," /* denom pub/sig */ - " s:o, s:o," /* coin pub/sig */ - " s:o}", /* coin_bks */ - "denom_pub", GNUNET_JSON_from_rsa_public_key (pk->key.rsa_public_key), - "denom_sig", GNUNET_JSON_from_rsa_signature (denom_sig->rsa_signature), - "coin_pub", GNUNET_JSON_from_data_auto (&pr.coin_pub), - "coin_sig", GNUNET_JSON_from_data_auto (&coin_sig), - "coin_blind_key_secret", GNUNET_JSON_from_data_auto (&ps->blinding_key) - ); - if (NULL == payback_obj) - { - GNUNET_break (0); - return NULL; - } - - ph = GNUNET_new (struct TALER_EXCHANGE_PaybackHandle); - ph->coin_pub = pr.coin_pub; - ph->exchange = exchange; - ph->pk = pk; - ph->cb = payback_cb; - ph->cb_cls = payback_cb_cls; - ph->url = TEAH_path_to_url (exchange, "/payback"); - - ph->json_enc = json_dumps (payback_obj, - JSON_COMPACT); - json_decref (payback_obj); - if (NULL == ph->json_enc) - { - GNUNET_break (0); - GNUNET_free (ph->url); - GNUNET_free (ph); - return NULL; - } - eh = TEL_curl_easy_get (ph->url); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for payback: `%s'\n", - ph->url); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDS, - ph->json_enc)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDSIZE, - strlen (ph->json_enc))); - ctx = TEAH_handle_to_context (exchange); - ph->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_payback_finished, - ph); - return ph; -} - - -/** - * Cancel a payback request. This function cannot be used on a - * request handle if the callback was already invoked. - * - * @param ph the payback handle - */ -void -TALER_EXCHANGE_payback_cancel (struct TALER_EXCHANGE_PaybackHandle *ph) -{ - if (NULL != ph->job) - { - GNUNET_CURL_job_cancel (ph->job); - ph->job = NULL; - } - GNUNET_free (ph->url); - GNUNET_free (ph->json_enc); - GNUNET_free (ph); -} - - -/* end of exchange_api_payback.c */ diff --git a/src/exchange-lib/exchange_api_refresh.c b/src/exchange-lib/exchange_api_refresh.c @@ -1,1678 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015, 2016, 2017 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 exchange-lib/exchange_api_refresh.c - * @brief Implementation of the /refresh/melt+reveal requests 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 "curl_defaults.h" - - -/* ********************* /refresh/ common ***************************** */ - -/* structures for committing refresh data to disk before doing the - network interaction(s) */ - -GNUNET_NETWORK_STRUCT_BEGIN - -/** - * Header of serialized information about a coin we are melting. - */ -struct MeltedCoinP -{ - /** - * Private key of the coin. - */ - struct TALER_CoinSpendPrivateKeyP coin_priv; - - /** - * Amount this coin contributes to the melt, including fee. - */ - struct TALER_AmountNBO melt_amount_with_fee; - - /** - * The applicable fee for withdrawing a coin of this denomination - */ - struct TALER_AmountNBO fee_melt; - - /** - * The original value of the coin. - */ - struct TALER_AmountNBO original_value; - - /** - * Transfer private keys for each cut-and-choose dimension. - */ - struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA]; - - /** - * Timestamp indicating when coins of this denomination become invalid. - */ - struct GNUNET_TIME_AbsoluteNBO expire_deposit; - - /** - * Size of the encoded public key that follows. - */ - uint16_t pbuf_size; - - /** - * Size of the encoded signature that follows. - */ - uint16_t sbuf_size; - - /* Followed by serializations of: - 1) struct TALER_DenominationPublicKey pub_key; - 2) struct TALER_DenominationSignature sig; - */ -}; - - -/** - * Header of serialized data about a melt operation, suitable for - * persisting it on disk. - */ -struct MeltDataP -{ - - /** - * Hash over the melting session. - */ - struct TALER_RefreshCommitmentP rc; - - /** - * Number of coins we are melting, in NBO - */ - uint16_t num_melted_coins GNUNET_PACKED; - - /** - * Number of coins we are creating, in NBO - */ - uint16_t num_fresh_coins GNUNET_PACKED; - - /* Followed by serializations of: - 1) struct MeltedCoinP melted_coins[num_melted_coins]; - 2) struct TALER_EXCHANGE_DenomPublicKey fresh_pks[num_fresh_coins]; - 3) TALER_CNC_KAPPA times: - 3a) struct TALER_PlanchetSecretsP fresh_coins[num_fresh_coins]; - */ -}; - - -GNUNET_NETWORK_STRUCT_END - - -/** - * 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; - - /** - * Transfer private keys for each cut-and-choose dimension. - */ - struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA]; - - /** - * Timestamp indicating when coins of this denomination become invalid. - */ - struct GNUNET_TIME_Absolute expire_deposit; - - /** - * Denomination key of the original coin. - */ - struct TALER_DenominationPublicKey pub_key; - - /** - * Exchange's signature over the coin. - */ - struct TALER_DenominationSignature sig; - -}; - - -/** - * Melt data in non-serialized format for convenient processing. - */ -struct MeltData -{ - - /** - * Hash over the committed data during refresh operation. - */ - struct TALER_RefreshCommitmentP rc; - - /** - * Number of coins we are creating - */ - uint16_t num_fresh_coins; - - /** - * Information about the melted coin. - */ - struct MeltedCoin melted_coin; - - /** - * Array of @e num_fresh_coins denomination keys for the coins to be - * freshly exchangeed. - */ - struct TALER_DenominationPublicKey *fresh_pks; - - /** - * Arrays of @e num_fresh_coins with information about the fresh - * coins to be created, for each cut-and-choose dimension. - */ - struct TALER_PlanchetSecretsP *fresh_coins[TALER_CNC_KAPPA]; -}; - - -/** - * Free all information associated with a melted coin session. - * - * @param mc melted coin to release, the pointer itself is NOT - * freed (as it is typically not allocated by itself) - */ -static void -free_melted_coin (struct MeltedCoin *mc) -{ - if (NULL != mc->pub_key.rsa_public_key) - GNUNET_CRYPTO_rsa_public_key_free (mc->pub_key.rsa_public_key); - if (NULL != mc->sig.rsa_signature) - GNUNET_CRYPTO_rsa_signature_free (mc->sig.rsa_signature); -} - - -/** - * 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 (i.e. due to failures in #deserialize_melt_data()). - * - * @param md melting data to release, the pointer itself is NOT - * freed (as it is typically not allocated by itself) - */ -static void -free_melt_data (struct MeltData *md) -{ - free_melted_coin (&md->melted_coin); - if (NULL != md->fresh_pks) - { - for (unsigned int i=0;i<md->num_fresh_coins;i++) - if (NULL != md->fresh_pks[i].rsa_public_key) - GNUNET_CRYPTO_rsa_public_key_free (md->fresh_pks[i].rsa_public_key); - GNUNET_free (md->fresh_pks); - } - - for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) - GNUNET_free (md->fresh_coins[i]); - /* Finally, clean up a bit... - (NOTE: compilers might optimize this away, so this is - not providing any strong assurances that the key material - is purged.) */ - memset (md, - 0, - sizeof (struct MeltData)); -} - - -/** - * Serialize information about a coin we are melting. - * - * @param mc information to serialize - * @param buf buffer to write data in, NULL to just compute - * required size - * @param off offeset at @a buf to use - * @return number of bytes written to @a buf at @a off, or if - * @a buf is NULL, number of bytes required; 0 on error - */ -static size_t -serialize_melted_coin (const struct MeltedCoin *mc, - char *buf, - size_t off) -{ - struct MeltedCoinP mcp; - unsigned int i; - char *pbuf; - size_t pbuf_size; - char *sbuf; - size_t sbuf_size; - - sbuf_size = GNUNET_CRYPTO_rsa_signature_encode (mc->sig.rsa_signature, - &sbuf); - pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (mc->pub_key.rsa_public_key, - &pbuf); - if (NULL == buf) - { - GNUNET_free (sbuf); - GNUNET_free (pbuf); - return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size; - } - if ( (sbuf_size > UINT16_MAX) || - (pbuf_size > UINT16_MAX) ) - { - GNUNET_break (0); - return 0; - } - mcp.coin_priv = mc->coin_priv; - TALER_amount_hton (&mcp.melt_amount_with_fee, - &mc->melt_amount_with_fee); - TALER_amount_hton (&mcp.fee_melt, - &mc->fee_melt); - TALER_amount_hton (&mcp.original_value, - &mc->original_value); - for (i=0;i<TALER_CNC_KAPPA;i++) - mcp.transfer_priv[i] = mc->transfer_priv[i]; - mcp.expire_deposit = GNUNET_TIME_absolute_hton (mc->expire_deposit); - mcp.pbuf_size = htons ((uint16_t) pbuf_size); - mcp.sbuf_size = htons ((uint16_t) sbuf_size); - memcpy (&buf[off], - &mcp, - sizeof (struct MeltedCoinP)); - memcpy (&buf[off + sizeof (struct MeltedCoinP)], - pbuf, - pbuf_size); - memcpy (&buf[off + sizeof (struct MeltedCoinP) + pbuf_size], - sbuf, - sbuf_size); - GNUNET_free (sbuf); - GNUNET_free (pbuf); - return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size; -} - - -/** - * Deserialize information about a coin we are melting. - * - * @param[out] mc information to deserialize - * @param buf buffer to read data from - * @param size number of bytes available at @a buf to use - * @param[out] ok set to #GNUNET_NO to report errors - * @return number of bytes read from @a buf, 0 on error - */ -static size_t -deserialize_melted_coin (struct MeltedCoin *mc, - const char *buf, - size_t size, - int *ok) -{ - struct MeltedCoinP mcp; - unsigned int i; - size_t pbuf_size; - size_t sbuf_size; - size_t off; - - if (size < sizeof (struct MeltedCoinP)) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - memcpy (&mcp, - buf, - sizeof (struct MeltedCoinP)); - pbuf_size = ntohs (mcp.pbuf_size); - sbuf_size = ntohs (mcp.sbuf_size); - if (size < sizeof (struct MeltedCoinP) + pbuf_size + sbuf_size) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - off = sizeof (struct MeltedCoinP); - mc->pub_key.rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_decode (&buf[off], - pbuf_size); - off += pbuf_size; - mc->sig.rsa_signature - = GNUNET_CRYPTO_rsa_signature_decode (&buf[off], - sbuf_size); - off += sbuf_size; - if ( (NULL == mc->pub_key.rsa_public_key) || - (NULL == mc->sig.rsa_signature) ) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - - mc->coin_priv = mcp.coin_priv; - TALER_amount_ntoh (&mc->melt_amount_with_fee, - &mcp.melt_amount_with_fee); - TALER_amount_ntoh (&mc->fee_melt, - &mcp.fee_melt); - TALER_amount_ntoh (&mc->original_value, - &mcp.original_value); - for (i=0;i<TALER_CNC_KAPPA;i++) - mc->transfer_priv[i] = mcp.transfer_priv[i]; - mc->expire_deposit = GNUNET_TIME_absolute_ntoh (mcp.expire_deposit); - return off; -} - - -/** - * Serialize information about a denomination key. - * - * @param dk information to serialize - * @param buf buffer to write data in, NULL to just compute - * required size - * @param off offeset at @a buf to use - * @return number of bytes written to @a buf at @a off, or if - * @a buf is NULL, number of bytes required - */ -static size_t -serialize_denomination_key (const struct TALER_DenominationPublicKey *dk, - char *buf, - size_t off) -{ - char *pbuf; - size_t pbuf_size; - uint32_t be; - - pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (dk->rsa_public_key, - &pbuf); - if (NULL == buf) - { - GNUNET_free (pbuf); - return pbuf_size + sizeof (uint32_t); - } - be = htonl ((uint32_t) pbuf_size); - memcpy (&buf[off], - &be, - sizeof (uint32_t)); - memcpy (&buf[off + sizeof (uint32_t)], - pbuf, - pbuf_size); - GNUNET_free (pbuf); - return pbuf_size + sizeof (uint32_t); -} - - -/** - * Deserialize information about a denomination key. - * - * @param[out] dk information to deserialize - * @param buf buffer to read data from - * @param size number of bytes available at @a buf to use - * @param[out] ok set to #GNUNET_NO to report errors - * @return number of bytes read from @a buf, 0 on error - */ -static size_t -deserialize_denomination_key (struct TALER_DenominationPublicKey *dk, - const char *buf, - size_t size, - int *ok) -{ - size_t pbuf_size; - uint32_t be; - - if (size < sizeof (uint32_t)) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - memcpy (&be, - buf, - sizeof (uint32_t)); - pbuf_size = ntohl (be); - if (size < sizeof (uint32_t) + pbuf_size) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - dk->rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_decode (&buf[sizeof (uint32_t)], - pbuf_size); - if (NULL == dk->rsa_public_key) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - return sizeof (uint32_t) + pbuf_size; -} - - -/** - * Serialize information about a fresh coin we are generating. - * - * @param fc information to serialize - * @param buf buffer to write data in, NULL to just compute - * required size - * @param off offeset at @a buf to use - * @return number of bytes written to @a buf at @a off, or if - * @a buf is NULL, number of bytes required - */ -static size_t -serialize_fresh_coin (const struct TALER_PlanchetSecretsP *fc, - char *buf, - size_t off) -{ - if (NULL != buf) - memcpy (&buf[off], - fc, - sizeof (struct TALER_PlanchetSecretsP)); - return sizeof (struct TALER_PlanchetSecretsP); -} - - -/** - * Deserialize information about a fresh coin we are generating. - * - * @param[out] fc information to deserialize - * @param buf buffer to read data from - * @param size number of bytes available at @a buf to use - * @param[out] ok set to #GNUNET_NO to report errors - * @return number of bytes read from @a buf, 0 on error - */ -static size_t -deserialize_fresh_coin (struct TALER_PlanchetSecretsP *fc, - const char *buf, - size_t size, - int *ok) -{ - if (size < sizeof (struct TALER_PlanchetSecretsP)) - { - GNUNET_break (0); - *ok = GNUNET_NO; - return 0; - } - memcpy (fc, - buf, - sizeof (struct TALER_PlanchetSecretsP)); - return sizeof (struct TALER_PlanchetSecretsP); -} - - -/** - * Serialize melt data. - * - * @param md data to serialize - * @param[out] res_size size of buffer returned - * @return serialized melt data - */ -static char * -serialize_melt_data (const struct MeltData *md, - size_t *res_size) -{ - size_t size; - size_t asize; - char *buf; - - size = 0; - asize = (size_t) -1; /* make the compiler happy */ - buf = NULL; - /* we do 2 iterations, #1 to determine total size, #2 to - actually construct the buffer */ - do { - if (0 == size) - { - size = sizeof (struct MeltDataP); - } - else - { - struct MeltDataP *mdp; - - buf = GNUNET_malloc (size); - asize = size; /* just for invariant check later */ - size = sizeof (struct MeltDataP); - mdp = (struct MeltDataP *) buf; - mdp->rc = md->rc; - mdp->num_fresh_coins = htons (md->num_fresh_coins); - } - size += serialize_melted_coin (&md->melted_coin, - buf, - size); - for (unsigned int i=0;i<md->num_fresh_coins;i++) - size += serialize_denomination_key (&md->fresh_pks[i], - buf, - size); - for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) - for(unsigned int j=0;j<md->num_fresh_coins;j++) - size += serialize_fresh_coin (&md->fresh_coins[i][j], - buf, - size); - } while (NULL == buf); - GNUNET_assert (size == asize); - *res_size = size; - return buf; -} - - -/** - * Deserialize melt data. - * - * @param buf serialized data - * @param buf_size size of @a buf - * @return deserialized melt data, NULL on error - */ -static struct MeltData * -deserialize_melt_data (const char *buf, - size_t buf_size) -{ - struct MeltData *md; - struct MeltDataP mdp; - size_t off; - int ok; - - if (buf_size < sizeof (struct MeltDataP)) - return NULL; - memcpy (&mdp, - buf, - sizeof (struct MeltDataP)); - md = GNUNET_new (struct MeltData); - md->rc = mdp.rc; - md->num_fresh_coins = ntohs (mdp.num_fresh_coins); - md->fresh_pks = GNUNET_new_array (md->num_fresh_coins, - struct TALER_DenominationPublicKey); - for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) - md->fresh_coins[i] = GNUNET_new_array (md->num_fresh_coins, - struct TALER_PlanchetSecretsP); - off = sizeof (struct MeltDataP); - ok = GNUNET_YES; - off += deserialize_melted_coin (&md->melted_coin, - &buf[off], - buf_size - off, - &ok); - for (unsigned int i=0;(i<md->num_fresh_coins)&&(GNUNET_YES == ok);i++) - off += deserialize_denomination_key (&md->fresh_pks[i], - &buf[off], - buf_size - off, - &ok); - - for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) - for (unsigned int j=0;(j<md->num_fresh_coins)&&(GNUNET_YES == ok);j++) - off += deserialize_fresh_coin (&md->fresh_coins[i][j], - &buf[off], - buf_size - off, - &ok); - if (off != buf_size) - { - GNUNET_break (0); - ok = GNUNET_NO; - } - if (GNUNET_YES != ok) - { - free_melt_data (md); - GNUNET_free (md); - return NULL; - } - return md; -} - - -/** - * Melt (partially spent) coins to obtain fresh coins that are - * unlinkable to the original coin(s). Note that melting more - * than one coin in a single request will make those coins linkable, - * so the safest operation only melts one coin at a time. - * - * This API is typically used by a wallet. Note that to ensure that - * no money is lost in case of hardware failures, is operation does - * not actually initiate the request. Instead, it generates a buffer - * which the caller must store before proceeding with the actual call - * to #TALER_EXCHANGE_refresh_melt() that will generate the request. - * - * This function does verify that the given request data is internally - * consistent. However, the @a melts_sigs are only verified if - * @a check_sigs is set to #GNUNET_YES, as this may be relatively - * expensive and should be redundant. - * - * Aside from some non-trivial cryptographic operations that might - * take a bit of CPU time to complete, this function returns - * its result immediately and does not start any asynchronous - * processing. This function is also thread-safe. - * - * @param melt_priv private key of the coin to melt - * @param melt_amount amount specifying how much - * the coin will contribute to the melt (including fee) - * @param melt_sig signature affirming the - * validity of the public keys corresponding to the - * @a melt_priv private key - * @param melt_pk denomination key information - * record corresponding to the @a melt_sig - * validity of the keys - * @param check_sig verify the validity of the @a melt_sig signature - * @param fresh_pks_len length of the @a pks array - * @param fresh_pks array of @a pks_len denominations of fresh coins to create - * @param[out] res_size set to the size of the return value, or 0 on error - * @return NULL - * if the inputs are invalid (i.e. denomination key not with this exchange). - * Otherwise, pointer to a buffer of @a res_size to store persistently - * before proceeding to #TALER_EXCHANGE_refresh_melt(). - * Non-null results should be freed using GNUNET_free(). - */ -char * -TALER_EXCHANGE_refresh_prepare (const struct TALER_CoinSpendPrivateKeyP *melt_priv, - const struct TALER_Amount *melt_amount, - const struct TALER_DenominationSignature *melt_sig, - const struct TALER_EXCHANGE_DenomPublicKey *melt_pk, - int check_sig, - unsigned int fresh_pks_len, - const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks, - size_t *res_size) -{ - struct MeltData md; - char *buf; - struct TALER_Amount total; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA]; - struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA]; - - GNUNET_CRYPTO_eddsa_key_get_public (&melt_priv->eddsa_priv, - &coin_pub.eddsa_pub); - /* build up melt data structure */ - md.num_fresh_coins = fresh_pks_len; - md.melted_coin.coin_priv = *melt_priv; - md.melted_coin.melt_amount_with_fee = *melt_amount; - md.melted_coin.fee_melt = melt_pk->fee_refresh; - md.melted_coin.original_value = melt_pk->value; - md.melted_coin.expire_deposit - = melt_pk->expire_deposit; - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (melt_amount->currency, - &total)); - md.melted_coin.pub_key.rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_dup (melt_pk->key.rsa_public_key); - md.melted_coin.sig.rsa_signature - = GNUNET_CRYPTO_rsa_signature_dup (melt_sig->rsa_signature); - md.fresh_pks = GNUNET_new_array (fresh_pks_len, - struct TALER_DenominationPublicKey); - for (unsigned int i=0;i<fresh_pks_len;i++) - { - md.fresh_pks[i].rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_dup (fresh_pks[i].key.rsa_public_key); - if ( (GNUNET_OK != - TALER_amount_add (&total, - &total, - &fresh_pks[i].value)) || - (GNUNET_OK != - TALER_amount_add (&total, - &total, - &fresh_pks[i].fee_withdraw)) ) - { - GNUNET_break (0); - free_melt_data (&md); - return NULL; - } - } - /* verify that melt_amount is above total cost */ - if (1 == - TALER_amount_cmp (&total, - melt_amount) ) - { - /* Eh, this operation is more expensive than the - @a melt_amount. This is not OK. */ - GNUNET_break (0); - free_melt_data (&md); - return NULL; - } - - /* build up coins */ - for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) - { - struct GNUNET_CRYPTO_EcdhePrivateKey *tpk; - - tpk = GNUNET_CRYPTO_ecdhe_key_create (); - md.melted_coin.transfer_priv[i].ecdhe_priv = *tpk; - GNUNET_free (tpk); - - GNUNET_CRYPTO_ecdhe_key_get_public (&md.melted_coin.transfer_priv[i].ecdhe_priv, - &rce[i].transfer_pub.ecdhe_pub); - TALER_link_derive_transfer_secret (melt_priv, - &md.melted_coin.transfer_priv[i], - &trans_sec[i]); - md.fresh_coins[i] = GNUNET_new_array (fresh_pks_len, - struct TALER_PlanchetSecretsP); - rce[i].new_coins = GNUNET_new_array (fresh_pks_len, - struct TALER_RefreshCoinData); - for (unsigned int j=0;j<fresh_pks_len;j++) - { - struct TALER_PlanchetSecretsP *fc = &md.fresh_coins[i][j]; - struct TALER_RefreshCoinData *rcd = &rce[i].new_coins[j]; - struct TALER_PlanchetDetail pd; - - TALER_planchet_setup_refresh (&trans_sec[i], - j, - fc); - if (GNUNET_OK != - TALER_planchet_prepare (&md.fresh_pks[j], - fc, - &pd)) - { - GNUNET_break_op (0); - free_melt_data (&md); - return NULL; - } - rcd->dk = &md.fresh_pks[j]; - rcd->coin_ev = pd.coin_ev; - rcd->coin_ev_size = pd.coin_ev_size; - } - } - - /* Compute refresh commitment */ - TALER_refresh_get_commitment (&md.rc, - TALER_CNC_KAPPA, - fresh_pks_len, - rce, - &coin_pub, - melt_amount); - /* finally, serialize everything */ - buf = serialize_melt_data (&md, - res_size); - for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++) - { - for (unsigned int j = 0; j < fresh_pks_len; j++) - GNUNET_free_non_null (rce[i].new_coins[j].coin_ev); - GNUNET_free_non_null (rce[i].new_coins); - } - free_melt_data (&md); - return buf; -} - - -/* ********************* /refresh/melt ***************************** */ - - -/** - * @brief A /refresh/melt Handle - */ -struct TALER_EXCHANGE_RefreshMeltHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * JSON encoding of the request to POST. - */ - char *json_enc; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with refresh melt failure results. - */ - TALER_EXCHANGE_RefreshMeltCallback 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; -}; - - -/** - * Verify that the signature on the "200 OK" response - * from the exchange is valid. - * - * @param rmh melt handle - * @param json json reply with the signature - * @param[out] exchange_pub public key of the exchange used for the signature - * @param[out] noreveal_index set to the noreveal index selected by the exchange - * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not - */ -static int -verify_refresh_melt_signature_ok (struct TALER_EXCHANGE_RefreshMeltHandle *rmh, - const json_t *json, - struct TALER_ExchangePublicKeyP *exchange_pub, - uint32_t *noreveal_index) -{ - 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", noreveal_index), - GNUNET_JSON_spec_end() - }; - struct TALER_RefreshMeltConfirmationPS confirm; - - 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 = TALER_EXCHANGE_get_keys (rmh->exchange); - 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 <= *noreveal_index) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* verify signature by exchange */ - confirm.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT); - confirm.purpose.size = htonl (sizeof (struct TALER_RefreshMeltConfirmationPS)); - confirm.rc = rmh->md->rc; - confirm.noreveal_index = htonl (*noreveal_index); - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT, - &confirm.purpose, - &exchange_sig.eddsa_signature, - &exchange_pub->eddsa_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Verify that the signatures on the "403 FORBIDDEN" response from the - * exchange demonstrating customer double-spending are valid. - * - * @param rmh melt handle - * @param json json reply with the signature(s) and transaction history - * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not - */ -static int -verify_refresh_melt_signature_forbidden (struct TALER_EXCHANGE_RefreshMeltHandle *rmh, - const json_t *json) -{ - json_t *history; - struct TALER_Amount original_value; - struct TALER_Amount melt_value_with_fee; - struct TALER_Amount total; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("history", &history), - GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub), - TALER_JSON_spec_amount ("original_value", &original_value), - TALER_JSON_spec_amount ("requested_value", &melt_value_with_fee), - GNUNET_JSON_spec_end() - }; - const struct MeltedCoin *mc; - - /* parse JSON reply */ - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* Find out which coin was deemed problematic by the exchange */ - mc = &rmh->md->melted_coin; - - /* check basic coin properties */ - if (0 != TALER_amount_cmp (&original_value, - &mc->original_value)) - { - /* We disagree on the value of the coin */ - GNUNET_break_op (0); - json_decref (history); - return GNUNET_SYSERR; - } - if (0 != TALER_amount_cmp (&melt_value_with_fee, - &mc->melt_amount_with_fee)) - { - /* We disagree on the value of the coin */ - GNUNET_break_op (0); - json_decref (history); - return GNUNET_SYSERR; - } - - /* verify coin history */ - history = json_object_get (json, - "history"); - if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (original_value.currency, - &coin_pub, - history, - &total)) - { - GNUNET_break_op (0); - json_decref (history); - return GNUNET_SYSERR; - } - json_decref (history); - - /* check if melt operation was really too expensive given history */ - if (GNUNET_OK != - TALER_amount_add (&total, - &total, - &melt_value_with_fee)) - { - /* clearly not OK if our transaction would have caused - the overflow... */ - return GNUNET_OK; - } - - if (0 >= TALER_amount_cmp (&total, - &original_value)) - { - /* transaction should have still fit */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - - /* everything OK, valid proof of double-spending was provided */ - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /refresh/melt request. - * - * @param cls the `struct TALER_EXCHANGE_RefreshMeltHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_refresh_melt_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_RefreshMeltHandle *rmh = cls; - uint32_t noreveal_index = TALER_CNC_KAPPA; /* invalid value */ - struct TALER_ExchangePublicKeyP exchange_pub; - const json_t *j = response; - - rmh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - verify_refresh_melt_signature_ok (rmh, - j, - &exchange_pub, - &noreveal_index)) - { - GNUNET_break_op (0); - response_code = 0; - } - if (NULL != rmh->melt_cb) - { - rmh->melt_cb (rmh->melt_cb_cls, - response_code, - TALER_JSON_get_error_code (j), - noreveal_index, - (0 == response_code) ? NULL : &exchange_pub, - j); - rmh->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 */ - break; - case MHD_HTTP_FORBIDDEN: - /* Double spending; check signatures on transaction history */ - if (GNUNET_OK != - verify_refresh_melt_signature_forbidden (rmh, - j)) - { - GNUNET_break_op (0); - response_code = 0; - } - break; - case MHD_HTTP_UNAUTHORIZED: - /* 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 */ - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != rmh->melt_cb) - rmh->melt_cb (rmh->melt_cb_cls, - response_code, - TALER_JSON_get_error_code (j), - UINT32_MAX, - NULL, - j); - TALER_EXCHANGE_refresh_melt_cancel (rmh); -} - - -/** - * Submit a melt request to the exchange and get the exchange's - * response. - * - * This API is typically used by a wallet. Note that to ensure that - * no money is lost in case of hardware failures, the provided - * argument should have been constructed using - * #TALER_EXCHANGE_refresh_prepare and committed to persistent storage - * prior to calling this function. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param refresh_data_length size of the @a refresh_data (returned - * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare()) - * @param refresh_data the refresh data as returned from - #TALER_EXCHANGE_refresh_prepare()) - * @param melt_cb the callback to call with the result - * @param melt_cb_cls closure for @a melt_cb - * @return a handle for this request; NULL if the argument was invalid. - * In this case, neither callback will be called. - */ -struct TALER_EXCHANGE_RefreshMeltHandle * -TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange, - size_t refresh_data_length, - const char *refresh_data, - TALER_EXCHANGE_RefreshMeltCallback melt_cb, - void *melt_cb_cls) -{ - json_t *melt_obj; - struct TALER_EXCHANGE_RefreshMeltHandle *rmh; - CURL *eh; - struct GNUNET_CURL_Context *ctx; - struct MeltData *md; - struct TALER_CoinSpendSignatureP confirm_sig; - struct TALER_RefreshMeltCoinAffirmationPS melt; - - GNUNET_assert (GNUNET_YES == - TEAH_handle_is_ready (exchange)); - md = deserialize_melt_data (refresh_data, - refresh_data_length); - if (NULL == md) - { - GNUNET_break (0); - return NULL; - } - - melt.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); - melt.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS)); - melt.rc = md->rc; - TALER_amount_hton (&melt.amount_with_fee, - &md->melted_coin.melt_amount_with_fee); - TALER_amount_hton (&melt.melt_fee, - &md->melted_coin.fee_melt); - GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv, - &melt.coin_pub.eddsa_pub); - GNUNET_CRYPTO_eddsa_sign (&md->melted_coin.coin_priv.eddsa_priv, - &melt.purpose, - &confirm_sig.eddsa_signature); - melt_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}", - "coin_pub", - GNUNET_JSON_from_data_auto (&melt.coin_pub), - "denom_pub", - GNUNET_JSON_from_rsa_public_key (md->melted_coin.pub_key.rsa_public_key), - "denom_sig", - GNUNET_JSON_from_rsa_signature (md->melted_coin.sig.rsa_signature), - "confirm_sig", - GNUNET_JSON_from_data_auto (&confirm_sig), - "value_with_fee", - TALER_JSON_from_amount (&md->melted_coin.melt_amount_with_fee), - "rc", - GNUNET_JSON_from_data_auto (&melt.rc)); - if (NULL == melt_obj) - { - GNUNET_break (0); - free_melt_data (md); - return NULL; - } - - /* and now we can at last begin the actual request handling */ - rmh = GNUNET_new (struct TALER_EXCHANGE_RefreshMeltHandle); - rmh->exchange = exchange; - rmh->melt_cb = melt_cb; - rmh->melt_cb_cls = melt_cb_cls; - rmh->md = md; - rmh->url = TEAH_path_to_url (exchange, - "/refresh/melt"); - eh = TEL_curl_easy_get (rmh->url); - GNUNET_assert (NULL != (rmh->json_enc = - json_dumps (melt_obj, - JSON_COMPACT))); - json_decref (melt_obj); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDS, - rmh->json_enc)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDSIZE, - strlen (rmh->json_enc))); - ctx = TEAH_handle_to_context (exchange); - rmh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_refresh_melt_finished, - rmh); - return rmh; -} - - -/** - * Cancel a refresh execute request. This function cannot be used - * on a request handle if either callback was already invoked. - * - * @param rmh the refresh melt handle - */ -void -TALER_EXCHANGE_refresh_melt_cancel (struct TALER_EXCHANGE_RefreshMeltHandle *rmh) -{ - if (NULL != rmh->job) - { - GNUNET_CURL_job_cancel (rmh->job); - rmh->job = NULL; - } - free_melt_data (rmh->md); /* does not free 'md' itself */ - GNUNET_free (rmh->md); - GNUNET_free (rmh->url); - GNUNET_free (rmh->json_enc); - GNUNET_free (rmh); -} - - -/* ********************* /refresh/reveal ***************************** */ - - -/** - * @brief A /refresh/reveal Handle - */ -struct TALER_EXCHANGE_RefreshRevealHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * JSON encoding of the request to POST. - */ - char *json_enc; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_RefreshRevealCallback 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 /refresh/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] coin_privs array of length `num_fresh_coins`, initialized to contain private keys - * @param[out] sigs array of length `num_fresh_coins`, initialized to cointain RSA signatures - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static int -refresh_reveal_ok (struct TALER_EXCHANGE_RefreshRevealHandle *rrh, - const json_t *json, - struct TALER_CoinSpendPrivateKeyP *coin_privs, - struct TALER_DenominationSignature *sigs) -{ - json_t *jsona; - struct GNUNET_JSON_Specification outer_spec[] = { - GNUNET_JSON_spec_json ("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 (! json_is_array (jsona)) - { - /* We expected an array of coins */ - GNUNET_break_op (0); - GNUNET_JSON_parse_free (outer_spec); - 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); - GNUNET_JSON_parse_free (outer_spec); - return GNUNET_SYSERR; - } - for (unsigned int i=0;i<rrh->md->num_fresh_coins;i++) - { - const struct TALER_PlanchetSecretsP *fc; - struct TALER_DenominationPublicKey *pk; - json_t *jsonai; - struct GNUNET_CRYPTO_RsaSignature *blind_sig; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct GNUNET_HashCode coin_hash; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_rsa_signature ("ev_sig", &blind_sig), - GNUNET_JSON_spec_end() - }; - struct TALER_FreshCoin coin; - - fc = &rrh->md->fresh_coins[rrh->noreveal_index][i]; - pk = &rrh->md->fresh_pks[i]; - jsonai = json_array_get (jsona, i); - GNUNET_assert (NULL != jsonai); - - if (GNUNET_OK != - GNUNET_JSON_parse (jsonai, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (outer_spec); - return GNUNET_SYSERR; - } - - /* needed to verify the signature, and we didn't store it earlier, - hence recomputing it here... */ - GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv, - &coin_pub.eddsa_pub); - GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub, - sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), - &coin_hash); - if (GNUNET_OK != - TALER_planchet_to_coin (pk, - blind_sig, - fc, - &coin_hash, - &coin)) - { - GNUNET_break_op (0); - GNUNET_CRYPTO_rsa_signature_free (blind_sig); - GNUNET_JSON_parse_free (outer_spec); - return GNUNET_SYSERR; - } - GNUNET_CRYPTO_rsa_signature_free (blind_sig); - coin_privs[i] = coin.coin_priv; - sigs[i] = coin.sig; - } - GNUNET_JSON_parse_free (outer_spec); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /refresh/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_RefreshRevealHandle *rrh = cls; - const json_t *j = response; - - rrh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - { - struct TALER_CoinSpendPrivateKeyP coin_privs[rrh->md->num_fresh_coins]; - struct TALER_DenominationSignature sigs[rrh->md->num_fresh_coins]; - int ret; - - memset (sigs, 0, sizeof (sigs)); - ret = refresh_reveal_ok (rrh, - j, - coin_privs, - sigs); - if (GNUNET_OK != ret) - { - response_code = 0; - } - else - { - rrh->reveal_cb (rrh->reveal_cb_cls, - MHD_HTTP_OK, - TALER_EC_NONE, - rrh->md->num_fresh_coins, - coin_privs, - sigs, - j); - rrh->reveal_cb = NULL; - } - for (unsigned int i=0;i<rrh->md->num_fresh_coins;i++) - if (NULL != sigs[i].rsa_signature) - GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature); - } - 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_CONFLICT: - /* Nothing really to verify, exchange says our reveal is inconsitent - with our commitment, so either side is buggy; we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != rrh->reveal_cb) - rrh->reveal_cb (rrh->reveal_cb_cls, - response_code, - TALER_JSON_get_error_code (j), - 0, - NULL, - NULL, - j); - TALER_EXCHANGE_refresh_reveal_cancel (rrh); -} - - -/** - * Submit a /refresh/reval request to the exchange and get the exchange's - * response. - * - * This API is typically used by a wallet. Note that to ensure that - * no money is lost in case of hardware failures, the provided - * arguments should have been committed to persistent storage - * prior to calling this function. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param refresh_data_length size of the @a refresh_data (returned - * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare()) - * @param refresh_data the refresh data as returned from - #TALER_EXCHANGE_refresh_prepare()) - * @param noreveal_index response from the exchange to the - * #TALER_EXCHANGE_refresh_melt() invocation - * @param reveal_cb the callback to call with the final result of the - * refresh operation - * @param reveal_cb_cls closure for the above callback - * @return a handle for this request; NULL if the argument was invalid. - * In this case, neither callback will be called. - */ -struct TALER_EXCHANGE_RefreshRevealHandle * -TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange, - size_t refresh_data_length, - const char *refresh_data, - uint32_t noreveal_index, - TALER_EXCHANGE_RefreshRevealCallback reveal_cb, - void *reveal_cb_cls) -{ - struct TALER_EXCHANGE_RefreshRevealHandle *rrh; - json_t *transfer_privs; - json_t *new_denoms_h; - json_t *coin_evs; - json_t *reveal_obj; - CURL *eh; - struct GNUNET_CURL_Context *ctx; - struct MeltData *md; - struct TALER_TransferPublicKeyP transfer_pub; - - GNUNET_assert (GNUNET_YES == - TEAH_handle_is_ready (exchange)); - md = deserialize_melt_data (refresh_data, - refresh_data_length); - if (NULL == md) - { - GNUNET_break (0); - return NULL; - } - 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; - } - - /* now transfer_pub */ - GNUNET_CRYPTO_ecdhe_key_get_public (&md->melted_coin.transfer_priv[noreveal_index].ecdhe_priv, - &transfer_pub.ecdhe_pub); - - /* now new_denoms */ - GNUNET_assert (NULL != (new_denoms_h = json_array ())); - GNUNET_assert (NULL != (coin_evs = json_array ())); - for (unsigned int i=0;i<md->num_fresh_coins;i++) - { - struct GNUNET_HashCode denom_hash; - struct TALER_PlanchetDetail pd; - - GNUNET_CRYPTO_rsa_public_key_hash (md->fresh_pks[i].rsa_public_key, - &denom_hash); - GNUNET_assert (0 == - json_array_append_new (new_denoms_h, - GNUNET_JSON_from_data_auto (&denom_hash))); - - if (GNUNET_OK != - TALER_planchet_prepare (&md->fresh_pks[i], - &md->fresh_coins[noreveal_index][i], - &pd)) - { - /* This should have been noticed during the preparation stage. */ - GNUNET_break (0); - json_decref (new_denoms_h); - json_decref (coin_evs); - return NULL; - } - GNUNET_assert (0 == - json_array_append_new (coin_evs, - GNUNET_JSON_from_data (pd.coin_ev, - pd.coin_ev_size))); - GNUNET_free (pd.coin_ev); - } - - /* 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->melted_coin.transfer_priv[j]))); - } - - /* build main JSON request */ - reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o}", - "rc", - GNUNET_JSON_from_data_auto (&md->rc), - "transfer_pub", - GNUNET_JSON_from_data_auto (&transfer_pub), - "transfer_privs", - transfer_privs, - "new_denoms_h", - new_denoms_h, - "coin_evs", - coin_evs); - if (NULL == reveal_obj) - { - GNUNET_break (0); - return NULL; - } - - /* finally, we can actually issue the request */ - rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshRevealHandle); - rrh->exchange = exchange; - rrh->noreveal_index = noreveal_index; - rrh->reveal_cb = reveal_cb; - rrh->reveal_cb_cls = reveal_cb_cls; - rrh->md = md; - rrh->url = TEAH_path_to_url (rrh->exchange, - "/refresh/reveal"); - - eh = TEL_curl_easy_get (rrh->url); - GNUNET_assert (NULL != (rrh->json_enc = - json_dumps (reveal_obj, - JSON_COMPACT))); - json_decref (reveal_obj); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDS, - rrh->json_enc)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDSIZE, - strlen (rrh->json_enc))); - ctx = TEAH_handle_to_context (rrh->exchange); - rrh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_refresh_reveal_finished, - rrh); - return rrh; -} - - -/** - * Cancel a refresh reveal request. This function cannot be used - * on a request handle if the callback was already invoked. - * - * @param rrh the refresh reval handle - */ -void -TALER_EXCHANGE_refresh_reveal_cancel (struct TALER_EXCHANGE_RefreshRevealHandle *rrh) -{ - if (NULL != rrh->job) - { - GNUNET_CURL_job_cancel (rrh->job); - rrh->job = NULL; - } - GNUNET_free (rrh->url); - GNUNET_free (rrh->json_enc); - free_melt_data (rrh->md); /* does not free 'md' itself */ - GNUNET_free (rrh->md); - GNUNET_free (rrh); -} - - -/* end of exchange_api_refresh.c */ diff --git a/src/exchange-lib/exchange_api_refresh_link.c b/src/exchange-lib/exchange_api_refresh_link.c @@ -1,444 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015, 2016 GNUnet e.V. - - 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 exchange-lib/exchange_api_refresh_link.c - * @brief Implementation of the /refresh/link request of the exchange's HTTP API - * @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 "curl_defaults.h" - - -/** - * @brief A /refresh/link Handle - */ -struct TALER_EXCHANGE_RefreshLinkHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_RefreshLinkCallback link_cb; - - /** - * Closure for @e cb. - */ - void *link_cb_cls; - - /** - * Private key of the coin, required to decode link information. - */ - struct TALER_CoinSpendPrivateKeyP coin_priv; - -}; - - -/** - * Parse the provided linkage data from the "200 OK" response - * for one of the coins. - * - * @param rlh refresh link handle - * @param json json reply with the data for one coin - * @param coin_num number of the coin to decode - * @param trans_pub our transfer public key - * @param[out] coin_priv where to return private coin key - * @param[out] sig where to return private coin signature - * @param[out] pub where to return the public key for the coin - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -static int -parse_refresh_link_coin (const struct TALER_EXCHANGE_RefreshLinkHandle *rlh, - const json_t *json, - unsigned int coin_num, - const struct TALER_TransferPublicKeyP *trans_pub, - struct TALER_CoinSpendPrivateKeyP *coin_priv, - struct TALER_DenominationSignature *sig, - struct TALER_DenominationPublicKey *pub) -{ - struct GNUNET_CRYPTO_RsaSignature *bsig; - struct GNUNET_CRYPTO_RsaPublicKey *rpub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_rsa_public_key ("denom_pub", &rpub), - GNUNET_JSON_spec_rsa_signature ("ev_sig", &bsig), - GNUNET_JSON_spec_end() - }; - struct TALER_TransferSecretP secret; - struct TALER_PlanchetSecretsP fc; - - /* parse reply */ - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - TALER_link_recover_transfer_secret (trans_pub, - &rlh->coin_priv, - &secret); - TALER_planchet_setup_refresh (&secret, - coin_num, - &fc); - - /* extract coin and signature */ - *coin_priv = fc.coin_priv; - sig->rsa_signature - = GNUNET_CRYPTO_rsa_unblind (bsig, - &fc.blinding_key.bks, - rpub); - /* clean up */ - pub->rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (rpub); - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * Parse the provided linkage data from the "200 OK" response - * for one of the coins. - * - * @param[in,out] rlh refresh link 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 int -parse_refresh_link_ok (struct TALER_EXCHANGE_RefreshLinkHandle *rlh, - const json_t *json) -{ - unsigned int session; - unsigned int num_coins; - int ret; - - if (! json_is_array (json)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - num_coins = 0; - /* Theoretically, a coin may have been melted repeatedly - into different sessions; so the response is an array - which contains information by melting session. That - array contains another array. However, our API returns - a single 1d array, so we flatten the 2d array that is - returned into a single array. Note that usually a coin - is melted at most once, and so we'll only run this - loop once for 'session=0' in most cases. - - num_coins tracks the size of the 1d array we return, - whilst 'i' and 'session' track the 2d array. */ - for (session=0;session<json_array_size (json); session++) - { - json_t *jsona; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("new_coins", &jsona), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (json, - session), - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (! json_is_array (jsona)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - - /* count all coins over all sessions */ - num_coins += json_array_size (jsona); - GNUNET_JSON_parse_free (spec); - } - /* Now that we know how big the 1d array is, allocate - and fill it. */ - { - unsigned int off_coin; /* index into 1d array */ - unsigned int i; - struct TALER_CoinSpendPrivateKeyP coin_privs[num_coins]; - struct TALER_DenominationSignature sigs[num_coins]; - struct TALER_DenominationPublicKey pubs[num_coins]; - - memset (sigs, 0, sizeof (sigs)); - memset (pubs, 0, sizeof (pubs)); - off_coin = 0; - for (session=0;session<json_array_size (json); session++) - { - json_t *jsona; - struct TALER_TransferPublicKeyP trans_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("new_coins", - &jsona), - GNUNET_JSON_spec_fixed_auto ("transfer_pub", - &trans_pub), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (json, - session), - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (! json_is_array (jsona)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - - /* decode all coins */ - for (i=0;i<json_array_size (jsona);i++) - { - GNUNET_assert (i + off_coin < num_coins); - if (GNUNET_OK != - parse_refresh_link_coin (rlh, - json_array_get (jsona, - i), - i, - &trans_pub, - &coin_privs[i+off_coin], - &sigs[i+off_coin], - &pubs[i+off_coin])) - { - GNUNET_break_op (0); - break; - } - } - /* check if we really got all, then invoke callback */ - off_coin += i; - if (i != json_array_size (jsona)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - GNUNET_JSON_parse_free (spec); - break; - } - GNUNET_JSON_parse_free (spec); - } /* end of for (session) */ - - if (off_coin == num_coins) - { - rlh->link_cb (rlh->link_cb_cls, - MHD_HTTP_OK, - TALER_EC_NONE, - num_coins, - coin_privs, - sigs, - pubs, - json); - rlh->link_cb = NULL; - ret = GNUNET_OK; - } - else - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - } - - /* clean up */ - GNUNET_assert (off_coin <= num_coins); - for (i=0;i<off_coin;i++) - { - if (NULL != sigs[i].rsa_signature) - GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature); - if (NULL != pubs[i].rsa_public_key) - GNUNET_CRYPTO_rsa_public_key_free (pubs[i].rsa_public_key); - } - } - return ret; -} - - -/** - * Function called when we're done processing the - * HTTP /refresh/link request. - * - * @param cls the `struct TALER_EXCHANGE_RefreshLinkHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_refresh_link_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_RefreshLinkHandle *rlh = cls; - const json_t *j = response; - - rlh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - parse_refresh_link_ok (rlh, - j)) - { - GNUNET_break_op (0); - response_code = 0; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* 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: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != rlh->link_cb) - rlh->link_cb (rlh->link_cb_cls, - response_code, - TALER_JSON_get_error_code (j), - 0, - NULL, - NULL, - NULL, - j); - TALER_EXCHANGE_refresh_link_cancel (rlh); -} - - -/** - * Submit a link request to the exchange and get the exchange's response. - * - * This API is typically not used by anyone, it is more a threat - * against those trying to receive a funds transfer by abusing the - * /refresh protocol. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param coin_priv private key to request link data for - * @param link_cb the callback to call with the useful result of the - * refresh operation the @a coin_priv was involved in (if any) - * @param link_cb_cls closure for @a link_cb - * @return a handle for this request - */ -struct TALER_EXCHANGE_RefreshLinkHandle * -TALER_EXCHANGE_refresh_link (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_CoinSpendPrivateKeyP *coin_priv, - TALER_EXCHANGE_RefreshLinkCallback link_cb, - void *link_cb_cls) -{ - struct TALER_EXCHANGE_RefreshLinkHandle *rlh; - CURL *eh; - struct GNUNET_CURL_Context *ctx; - struct TALER_CoinSpendPublicKeyP coin_pub; - char *pub_str; - char *arg_str; - - if (GNUNET_YES != - TEAH_handle_is_ready (exchange)) - { - GNUNET_break (0); - return NULL; - } - - GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, - &coin_pub.eddsa_pub); - pub_str = GNUNET_STRINGS_data_to_string_alloc (&coin_pub, - sizeof (struct TALER_CoinSpendPublicKeyP)); - GNUNET_asprintf (&arg_str, - "/refresh/link?coin_pub=%s", - pub_str); - GNUNET_free (pub_str); - - rlh = GNUNET_new (struct TALER_EXCHANGE_RefreshLinkHandle); - rlh->exchange = exchange; - rlh->link_cb = link_cb; - rlh->link_cb_cls = link_cb_cls; - rlh->coin_priv = *coin_priv; - rlh->url = TEAH_path_to_url (exchange, arg_str); - GNUNET_free (arg_str); - - - eh = TEL_curl_easy_get (rlh->url); - ctx = TEAH_handle_to_context (exchange); - rlh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_refresh_link_finished, - rlh); - return rlh; -} - - -/** - * Cancel a refresh link request. This function cannot be used - * on a request handle if the callback was already invoked. - * - * @param rlh the refresh link handle - */ -void -TALER_EXCHANGE_refresh_link_cancel (struct TALER_EXCHANGE_RefreshLinkHandle *rlh) -{ - if (NULL != rlh->job) - { - GNUNET_CURL_job_cancel (rlh->job); - rlh->job = NULL; - } - GNUNET_free (rlh->url); - GNUNET_free (rlh); -} - - -/* end of exchange_api_refresh_link.c */ diff --git a/src/exchange-lib/exchange_api_refund.c b/src/exchange-lib/exchange_api_refund.c @@ -1,416 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015, 2016 GNUnet e.V. - - 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 exchange-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 "curl_defaults.h" - - -/** - * @brief A Refund Handle - */ -struct TALER_EXCHANGE_RefundHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * JSON encoding of the request to POST. - */ - char *json_enc; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_RefundResultCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Information the exchange should sign in response. - */ - struct TALER_RefundConfirmationPS depconf; - -}; - - -/** - * Verify that the signature on the "200 OK" response - * from the exchange is valid. - * - * @param rh refund handle - * @param json json reply with the signature - * @param[out] exchange_pub set to the exchange's public key - * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not - */ -static int -verify_refund_signature_ok (const struct TALER_EXCHANGE_RefundHandle *rh, - 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 ("sig", &exchange_sig), - GNUNET_JSON_spec_fixed_auto ("pub", exchange_pub), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - key_state = TALER_EXCHANGE_get_keys (rh->exchange); - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_state, - exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND, - &rh->depconf.purpose, - &exchange_sig.eddsa_signature, - &exchange_pub->eddsa_pub)) - { - 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; - struct TALER_ExchangePublicKeyP exchange_pub; - struct TALER_ExchangePublicKeyP *ep = NULL; - const json_t *j = response; - - rh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - verify_refund_signature_ok (rh, - j, - &exchange_pub)) - { - GNUNET_break_op (0); - response_code = 0; - } - else - { - ep = &exchange_pub; - } - 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: - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_GONE: - /* Kind of normal: the money was already sent to the merchant - (it was too late for the refund). */ - break; - case MHD_HTTP_PRECONDITION_FAILED: - /* Client request was inconsistent; might be a currency missmatch - problem. */ - break; - case MHD_HTTP_CONFLICT: - /* Two refund requests were made about the same deposit, but - carrying different refund transaction ids. */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - rh->cb (rh->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - ep, - j); - TALER_EXCHANGE_refund_cancel (rh); -} - - -/** - * Submit a refund request to the exchange and get the exchange's - * response. This API is used by a merchant. Note that - * while we return the response verbatim to the caller for further - * processing, we do already verify that the response is well-formed - * (i.e. that signatures included in the response are all valid). If - * the exchange's reply is not well-formed, we return an HTTP status code - * of zero to @a cb. - * - * The @a exchange must be ready to operate (i.e. have - * finished processing the /keys reply). If this check fails, we do - * NOT initiate the transaction with the exchange and instead return NULL. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param amount the amount to be refunded; must be larger than the refund fee - * (as that fee is still being subtracted), and smaller than the amount - * (with deposit fee) of the original deposit contribution of this coin - * @param refund_fee fee applicable to this coin for the refund - * @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded - * @param coin_pub coin’s public key of the coin from the original deposit operation - * @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation); - * this is needed as we may first do a partial refund and later a full refund. If both - * refunds are also over the same amount, we need the @a rtransaction_id to make the disjoint - * refund requests different (as requests are idempotent and otherwise the 2nd refund might not work). - * @param merchant_priv the private key of the merchant, used to generate signature for refund request - * @param cb the callback to call when a reply for this request is available - * @param cb_cls closure for the above callback - * @return a handle for this request; NULL if the inputs are invalid (i.e. - * signatures fail to verify). In this case, the callback is not called. - */ -struct TALER_EXCHANGE_RefundHandle * -TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_Amount *amount, - const struct TALER_Amount *refund_fee, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - uint64_t rtransaction_id, - const struct TALER_MerchantPrivateKeyP *merchant_priv, - TALER_EXCHANGE_RefundResultCallback cb, - void *cb_cls) -{ - struct TALER_RefundRequestPS rr; - struct TALER_MerchantSignatureP merchant_sig; - - GNUNET_assert (GNUNET_YES == - TEAH_handle_is_ready (exchange)); - rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND); - rr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS)); - rr.h_contract_terms = *h_contract_terms; - rr.coin_pub = *coin_pub; - GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, - &rr.merchant.eddsa_pub); - rr.rtransaction_id = GNUNET_htonll (rtransaction_id); - TALER_amount_hton (&rr.refund_amount, - amount); - TALER_amount_hton (&rr.refund_fee, - refund_fee); - GNUNET_assert (GNUNET_OK == - GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv, - &rr.purpose, - &merchant_sig.eddsa_sig)); - return TALER_EXCHANGE_refund2 (exchange, - amount, - refund_fee, - h_contract_terms, - coin_pub, - rtransaction_id, - &rr.merchant, - &merchant_sig, - cb, - cb_cls); -} - - -/** - * Submit a refund request to the exchange and get the exchange's - * response. This API is used by a merchant. Note that - * while we return the response verbatim to the caller for further - * processing, we do already verify that the response is well-formed - * (i.e. that signatures included in the response are all valid). If - * the exchange's reply is not well-formed, we return an HTTP status code - * of zero to @a cb. - * - * The @a exchange must be ready to operate (i.e. have - * finished processing the /keys reply). If this check fails, we do - * NOT initiate the transaction with the exchange and instead return NULL. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param amount the amount to be refunded; must be larger than the refund fee - * (as that fee is still being subtracted), and smaller than the amount - * (with deposit fee) of the original deposit contribution of this coin - * @param refund_fee fee applicable to this coin for the refund - * @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded - * @param coin_pub coin’s public key of the coin from the original deposit operation - * @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation); - * this is needed as we may first do a partial refund and later a full refund. If both - * refunds are also over the same amount, we need the @a rtransaction_id to make the disjoint - * refund requests different (as requests are idempotent and otherwise the 2nd refund might not work). - * @param merchant_pub public key of the merchant - * @param merchant_sig signature affirming the refund from the merchant - * @param cb the callback to call when a reply for this request is available - * @param cb_cls closure for the above callback - * @return a handle for this request; NULL if the inputs are invalid (i.e. - * signatures fail to verify). In this case, the callback is not called. - */ -struct TALER_EXCHANGE_RefundHandle * -TALER_EXCHANGE_refund2 (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_Amount *amount, - const struct TALER_Amount *refund_fee, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - uint64_t rtransaction_id, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct TALER_MerchantSignatureP *merchant_sig, - TALER_EXCHANGE_RefundResultCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_RefundHandle *rh; - struct GNUNET_CURL_Context *ctx; - json_t *refund_obj; - CURL *eh; - -refund_obj = json_pack ("{s:o, s:o," /* amount/fee */ - " s:o, s:o," /* h_contract_terms, coin_pub */ - " s:I," /* rtransaction id */ - " s:o, s:o}", /* merchant_pub, merchant_sig */ - "refund_amount", TALER_JSON_from_amount (amount), - "refund_fee", TALER_JSON_from_amount (refund_fee), - "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms), - "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), - "rtransaction_id", (json_int_t) rtransaction_id, - "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub), - "merchant_sig", GNUNET_JSON_from_data_auto (merchant_sig) - ); - if (NULL == refund_obj) - { - GNUNET_break (0); - return NULL; - } - - rh = GNUNET_new (struct TALER_EXCHANGE_RefundHandle); - rh->exchange = exchange; - rh->cb = cb; - rh->cb_cls = cb_cls; - rh->url = TEAH_path_to_url (exchange, "/refund"); - rh->depconf.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS)); - rh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND); - rh->depconf.h_contract_terms = *h_contract_terms; - rh->depconf.coin_pub = *coin_pub; - rh->depconf.merchant = *merchant_pub; - rh->depconf.rtransaction_id = GNUNET_htonll (rtransaction_id); - TALER_amount_hton (&rh->depconf.refund_amount, - amount); - TALER_amount_hton (&rh->depconf.refund_fee, - refund_fee); - - eh = TEL_curl_easy_get (rh->url); - GNUNET_assert (NULL != (rh->json_enc = - json_dumps (refund_obj, - JSON_COMPACT))); - json_decref (refund_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for refund: `%s'\n", - rh->url); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDS, - rh->json_enc)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDSIZE, - strlen (rh->json_enc))); - ctx = TEAH_handle_to_context (exchange); - rh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_refund_finished, - rh); - return rh; -} - - -/** - * Cancel a refund permission request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param refund the refund permission request handle - */ -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); - GNUNET_free (refund->json_enc); - GNUNET_free (refund); -} - - -/* end of exchange_api_refund.c */ diff --git a/src/exchange-lib/exchange_api_reserve.c b/src/exchange-lib/exchange_api_reserve.c @@ -1,1203 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015 GNUnet e.V. - - 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 exchange-lib/exchange_api_reserve.c - * @brief Implementation of the /reserve requests 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_exchange_service.h" -#include "taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler_signatures.h" -#include "curl_defaults.h" - - -/* ********************** /reserve/status ********************** */ - -/** - * @brief A Withdraw Status Handle - */ -struct TALER_EXCHANGE_ReserveStatusHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ReserveStatusResultCallback cb; - - /** - * Public key of the reserve we are querying. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * Parse history given in JSON format and return it in binary - * format. - * - * @param exchange connection to the exchange we can use - * @param history JSON array with the history - * @param reserve_pub public key of the reserve to inspect - * @param currency currency we expect the balance to be in - * @param[out] balance final balance - * @param history_length number of entries in @a history - * @param[out] rhistory array of length @a history_length, set to the - * parsed history entries - * @return #GNUNET_OK if history was valid and @a rhistory and @a balance - * were set, - * #GNUNET_SYSERR if there was a protocol violation in @a history - */ -static int -parse_reserve_history (struct TALER_EXCHANGE_Handle *exchange, - const json_t *history, - const struct TALER_ReservePublicKeyP *reserve_pub, - const char *currency, - struct TALER_Amount *balance, - unsigned int history_length, - struct TALER_EXCHANGE_ReserveHistory *rhistory) -{ - struct GNUNET_HashCode uuid[history_length]; - unsigned int uuid_off; - struct TALER_Amount total_in; - struct TALER_Amount total_out; - size_t off; - - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_in)); - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (currency, - &total_out)); - uuid_off = 0; - for (off=0;off<history_length;off++) - { - json_t *transaction; - struct TALER_Amount amount; - const char *type; - struct GNUNET_JSON_Specification hist_spec[] = { - GNUNET_JSON_spec_string ("type", &type), - TALER_JSON_spec_amount ("amount", - &amount), - /* 'wire' and 'signature' are optional depending on 'type'! */ - GNUNET_JSON_spec_end() - }; - - transaction = json_array_get (history, - off); - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - hist_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - rhistory[off].amount = amount; - - if (0 == strcasecmp (type, - "DEPOSIT")) - { - const char *wire_url; - void *wire_reference; - size_t wire_reference_size; - struct GNUNET_TIME_Absolute timestamp; - - struct GNUNET_JSON_Specification withdraw_spec[] = { - GNUNET_JSON_spec_varsize ("wire_reference", - &wire_reference, - &wire_reference_size), - GNUNET_JSON_spec_absolute_time ("timestamp", - &timestamp), - GNUNET_JSON_spec_string ("sender_account_url", - &wire_url), - GNUNET_JSON_spec_end() - }; - - rhistory[off].type = TALER_EXCHANGE_RTT_DEPOSIT; - if (GNUNET_OK != - TALER_amount_add (&total_in, - &total_in, - &amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - withdraw_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - rhistory[off].details.in_details.sender_url = GNUNET_strdup (wire_url); - rhistory[off].details.in_details.wire_reference = wire_reference; - rhistory[off].details.in_details.wire_reference_size = wire_reference_size; - rhistory[off].details.in_details.timestamp = timestamp; - /* end type==DEPOSIT */ - } - else if (0 == strcasecmp (type, - "WITHDRAW")) - { - struct TALER_ReserveSignatureP sig; - struct TALER_WithdrawRequestPS withdraw_purpose; - struct GNUNET_JSON_Specification withdraw_spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &sig), - TALER_JSON_spec_amount_nbo ("withdraw_fee", - &withdraw_purpose.withdraw_fee), - GNUNET_JSON_spec_fixed_auto ("h_denom_pub", - &withdraw_purpose.h_denomination_pub), - GNUNET_JSON_spec_fixed_auto ("h_coin_envelope", - &withdraw_purpose.h_coin_envelope), - GNUNET_JSON_spec_end() - }; - unsigned int i; - - rhistory[off].type = TALER_EXCHANGE_RTT_WITHDRAWAL; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - withdraw_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - withdraw_purpose.purpose.size - = htonl (sizeof (withdraw_purpose)); - withdraw_purpose.purpose.purpose - = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); - withdraw_purpose.reserve_pub = *reserve_pub; - TALER_amount_hton (&withdraw_purpose.amount_with_fee, - &amount); - /* Check that the signature is a valid withdraw request */ - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, - &withdraw_purpose.purpose, - &sig.eddsa_signature, - &reserve_pub->eddsa_pub)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - /* TODO: check that withdraw fee matches expectations! */ - rhistory[off].details.out_authorization_sig - = json_object_get (transaction, - "signature"); - /* Check check that the same withdraw transaction - isn't listed twice by the exchange. We use the - "uuid" array to remember the hashes of all - purposes, and compare the hashes to find - duplicates. */ - GNUNET_CRYPTO_hash (&withdraw_purpose, - ntohl (withdraw_purpose.purpose.size), - &uuid[uuid_off]); - for (i=0;i<uuid_off;i++) - { - if (0 == memcmp (&uuid[uuid_off], - &uuid[i], - sizeof (struct GNUNET_HashCode))) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - } - uuid_off++; - - if (GNUNET_OK != - TALER_amount_add (&total_out, - &total_out, - &amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - /* end type==WITHDRAW */ - } - else if (0 == strcasecmp (type, - "PAYBACK")) - { - struct TALER_PaybackConfirmationPS pc; - struct GNUNET_TIME_Absolute timestamp; - const struct TALER_EXCHANGE_Keys *key_state; - struct GNUNET_JSON_Specification payback_spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &pc.coin_pub), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rhistory[off].details.payback_details.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rhistory[off].details.payback_details.exchange_pub), - GNUNET_JSON_spec_absolute_time_nbo ("timestamp", - &pc.timestamp), - GNUNET_JSON_spec_end() - }; - - rhistory[off].type = TALER_EXCHANGE_RTT_PAYBACK; - rhistory[off].amount = amount; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - payback_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - rhistory[off].details.payback_details.coin_pub = pc.coin_pub; - TALER_amount_hton (&pc.payback_amount, - &amount); - pc.purpose.size = htonl (sizeof (pc)); - pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK); - pc.reserve_pub = *reserve_pub; - timestamp = GNUNET_TIME_absolute_ntoh (pc.timestamp); - rhistory[off].details.payback_details.timestamp = timestamp; - - key_state = TALER_EXCHANGE_get_keys (exchange); - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_state, - &rhistory[off].details.payback_details.exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK, - &pc.purpose, - &rhistory[off].details.payback_details.exchange_sig.eddsa_signature, - &rhistory[off].details.payback_details.exchange_pub.eddsa_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&total_in, - &total_in, - &rhistory[off].amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* end type==PAYBACK */ - } - else if (0 == strcasecmp (type, - "CLOSING")) - { - const struct TALER_EXCHANGE_Keys *key_state; - struct TALER_ReserveCloseConfirmationPS rcc; - struct GNUNET_TIME_Absolute timestamp; - struct GNUNET_JSON_Specification closing_spec[] = { - GNUNET_JSON_spec_string ("receiver_account_details", - &rhistory[off].details.close_details.receiver_account_details), - GNUNET_JSON_spec_fixed_auto ("wtid", - &rhistory[off].details.close_details.wtid), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rhistory[off].details.close_details.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rhistory[off].details.close_details.exchange_pub), - TALER_JSON_spec_amount_nbo ("closing_fee", - &rcc.closing_fee), - GNUNET_JSON_spec_absolute_time_nbo ("timestamp", - &rcc.timestamp), - GNUNET_JSON_spec_end() - }; - - rhistory[off].type = TALER_EXCHANGE_RTT_CLOSE; - rhistory[off].amount = amount; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - closing_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - TALER_amount_hton (&rcc.closing_amount, - &amount); - GNUNET_CRYPTO_hash (rhistory[off].details.close_details.receiver_account_details, - strlen (rhistory[off].details.close_details.receiver_account_details) + 1, - &rcc.h_wire); - rcc.wtid = rhistory[off].details.close_details.wtid; - rcc.purpose.size = htonl (sizeof (rcc)); - rcc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED); - rcc.reserve_pub = *reserve_pub; - timestamp = GNUNET_TIME_absolute_ntoh (rcc.timestamp); - rhistory[off].details.close_details.timestamp = timestamp; - - key_state = TALER_EXCHANGE_get_keys (exchange); - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_state, - &rhistory[off].details.close_details.exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED, - &rcc.purpose, - &rhistory[off].details.close_details.exchange_sig.eddsa_signature, - &rhistory[off].details.close_details.exchange_pub.eddsa_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&total_out, - &total_out, - &rhistory[off].amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* end type==CLOSING */ - } - else - { - /* unexpected 'type', protocol incompatibility, complain! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - - /* check balance = total_in - total_out < withdraw-amount */ - if (GNUNET_SYSERR == - TALER_amount_subtract (balance, - &total_in, - &total_out)) - { - /* total_in < total_out, why did the exchange ever allow this!? */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Free memory (potentially) allocated by #parse_reserve_history(). - * - * @param rhistory result to free - * @param len number of entries in @a rhistory - */ -static void -free_rhistory (struct TALER_EXCHANGE_ReserveHistory *rhistory, - unsigned int len) -{ - for (unsigned int i=0;i<len;i++) - { - switch (rhistory[i].type) - { - case TALER_EXCHANGE_RTT_DEPOSIT: - GNUNET_free_non_null (rhistory[i].details.in_details.wire_reference); - GNUNET_free_non_null (rhistory[i].details.in_details.sender_url); - break; - case TALER_EXCHANGE_RTT_WITHDRAWAL: - break; - case TALER_EXCHANGE_RTT_PAYBACK: - break; - case TALER_EXCHANGE_RTT_CLOSE: - // should we free "receiver_account_details" ? - break; - } - } -} - - -/** - * Function called when we're done processing the - * HTTP /reserve/status request. - * - * @param cls the `struct TALER_EXCHANGE_ReserveStatusHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserve_status_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ReserveStatusHandle *rsh = cls; - const json_t *j = response; - - rsh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - { - /* TODO: move into separate function... */ - json_t *history; - unsigned int len; - struct TALER_Amount balance; - struct TALER_Amount balance_from_history; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount ("balance", &balance), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - response_code = 0; - break; - } - history = json_object_get (j, - "history"); - if (NULL == history) - { - GNUNET_break_op (0); - response_code = 0; - break; - } - len = json_array_size (history); - { - struct TALER_EXCHANGE_ReserveHistory rhistory[len]; - - memset (rhistory, 0, sizeof (rhistory)); - if (GNUNET_OK != - parse_reserve_history (rsh->exchange, - history, - &rsh->reserve_pub, - balance.currency, - &balance_from_history, - len, - rhistory)) - { - GNUNET_break_op (0); - response_code = 0; - } - if ( (0 != response_code) && - (0 != - TALER_amount_cmp (&balance_from_history, - &balance)) ) - { - /* exchange cannot add up balances!? */ - GNUNET_break_op (0); - response_code = 0; - } - if (0 != response_code) - { - rsh->cb (rsh->cb_cls, - response_code, - TALER_EC_NONE, - j, - &balance, - len, - rhistory); - rsh->cb = NULL; - } - free_rhistory (rhistory, - len); - } - } - 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_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != rsh->cb) - { - rsh->cb (rsh->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - j, - NULL, - 0, NULL); - rsh->cb = NULL; - } - TALER_EXCHANGE_reserve_status_cancel (rsh); -} - - -/** - * Submit a request to obtain the transaction history of a reserve - * from the exchange. Note that while we return the full response to the - * caller for further processing, we do already verify that the - * response is well-formed (i.e. that signatures included in the - * response are all valid and add up to the balance). If the exchange's - * reply is not well-formed, we return an HTTP status code of zero to - * @a cb. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param reserve_pub public key of the reserve to inspect - * @param cb the callback to call when a reply for this request is available - * @param cb_cls closure for the above callback - * @return a handle for this request; NULL if the inputs are invalid (i.e. - * signatures fail to verify). In this case, the callback is not called. - */ -struct TALER_EXCHANGE_ReserveStatusHandle * -TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_ReservePublicKeyP *reserve_pub, - TALER_EXCHANGE_ReserveStatusResultCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ReserveStatusHandle *rsh; - struct GNUNET_CURL_Context *ctx; - CURL *eh; - char *pub_str; - char *arg_str; - - if (GNUNET_YES != - TEAH_handle_is_ready (exchange)) - { - GNUNET_break (0); - return NULL; - } - pub_str = GNUNET_STRINGS_data_to_string_alloc (reserve_pub, - sizeof (struct TALER_ReservePublicKeyP)); - GNUNET_asprintf (&arg_str, - "/reserve/status?reserve_pub=%s", - pub_str); - GNUNET_free (pub_str); - rsh = GNUNET_new (struct TALER_EXCHANGE_ReserveStatusHandle); - rsh->exchange = exchange; - rsh->cb = cb; - rsh->cb_cls = cb_cls; - rsh->reserve_pub = *reserve_pub; - rsh->url = TEAH_path_to_url (exchange, - arg_str); - GNUNET_free (arg_str); - - eh = TEL_curl_easy_get (rsh->url); - ctx = TEAH_handle_to_context (exchange); - rsh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_NO, - &handle_reserve_status_finished, - rsh); - return rsh; -} - - -/** - * Cancel a reserve status request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param rsh the reserve status request handle - */ -void -TALER_EXCHANGE_reserve_status_cancel (struct TALER_EXCHANGE_ReserveStatusHandle *rsh) -{ - if (NULL != rsh->job) - { - GNUNET_CURL_job_cancel (rsh->job); - rsh->job = NULL; - } - GNUNET_free (rsh->url); - GNUNET_free (rsh); -} - - -/* ********************** /reserve/withdraw ********************** */ - -/** - * @brief A Withdraw Sign Handle - */ -struct TALER_EXCHANGE_ReserveWithdrawHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * JSON encoding of the request to POST. - */ - char *json_enc; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ReserveWithdrawResultCallback cb; - - /** - * Secrets of the planchet. - */ - struct TALER_PlanchetSecretsP ps; - - /** - * Denomination key we are withdrawing. - */ - const struct TALER_EXCHANGE_DenomPublicKey *pk; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Hash of the public key of the coin we are signing. - */ - struct GNUNET_HashCode c_hash; - - /** - * Public key of the reserve we are withdrawing from. - */ - struct TALER_ReservePublicKeyP reserve_pub; - -}; - - -/** - * We got a 200 OK response for the /reserve/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 wsh operation handle - * @param json reply from the exchange - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static int -reserve_withdraw_ok (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh, - const json_t *json) -{ - struct GNUNET_CRYPTO_RsaSignature *blind_sig; - struct TALER_FreshCoin fc; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_rsa_signature ("ev_sig", - &blind_sig), - 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_planchet_to_coin (&wsh->pk->key, - blind_sig, - &wsh->ps, - &wsh->c_hash, - &fc)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - GNUNET_JSON_parse_free (spec); - - /* signature is valid, return it to the application */ - wsh->cb (wsh->cb_cls, - MHD_HTTP_OK, - TALER_EC_NONE, - &fc.sig, - json); - /* make sure callback isn't called again after return */ - wsh->cb = NULL; - GNUNET_CRYPTO_rsa_signature_free (fc.sig.rsa_signature); - return GNUNET_OK; -} - - -/** - * We got a 403 FORBIDDEN response for the /reserve/withdraw operation. - * Check the signatures on the withdraw transactions in the provided - * history and that the balances add up. We don't do anything directly - * with the information, as the JSON will be returned to the application. - * However, our job is ensuring that the exchange followed the protocol, and - * this in particular means checking all of the signatures in the history. - * - * @param wsh operation handle - * @param json reply from the exchange - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static int -reserve_withdraw_payment_required (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh, - const json_t *json) -{ - struct TALER_Amount balance; - struct TALER_Amount balance_from_history; - struct TALER_Amount requested_amount; - json_t *history; - size_t len; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount ("balance", &balance), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - history = json_object_get (json, - "history"); - if (NULL == history) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* go over transaction history and compute - total incoming and outgoing amounts */ - len = json_array_size (history); - { - struct TALER_EXCHANGE_ReserveHistory *rhistory; - - /* Use heap allocation as "len" may be very big and thus this may - not fit on the stack. Use "GNUNET_malloc_large" as a malicious - exchange may theoretically try to crash us by giving a history - that does not fit into our memory. */ - rhistory = GNUNET_malloc_large (sizeof (struct TALER_EXCHANGE_ReserveHistory) * len); - if (NULL == rhistory) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - - if (GNUNET_OK != - parse_reserve_history (wsh->exchange, - history, - &wsh->reserve_pub, - balance.currency, - &balance_from_history, - len, - rhistory)) - { - GNUNET_break_op (0); - free_rhistory (rhistory, - len); - return GNUNET_SYSERR; - } - free_rhistory (rhistory, - len); - } - - if (0 != - TALER_amount_cmp (&balance_from_history, - &balance)) - { - /* exchange cannot add up balances!? */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* Compute how much we expected to charge to the reserve */ - if (GNUNET_OK != - TALER_amount_add (&requested_amount, - &wsh->pk->value, - &wsh->pk->fee_withdraw)) - { - /* Overflow here? Very strange, our CPU must be fried... */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - /* Check that funds were really insufficient */ - if (0 >= TALER_amount_cmp (&requested_amount, - &balance)) - { - /* Requested amount is smaller or equal to reported balance, - so this should not have failed. */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reserve/withdraw request. - * - * @param cls the `struct TALER_EXCHANGE_ReserveWithdrawHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserve_withdraw_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh = cls; - const json_t *j = response; - - wsh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - reserve_withdraw_ok (wsh, - j)) - { - GNUNET_break_op (0); - response_code = 0; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - /* The exchange says that the reserve has insufficient funds; - check the signatures in the history... */ - if (GNUNET_OK != - reserve_withdraw_payment_required (wsh, - j)) - { - GNUNET_break_op (0); - response_code = 0; - } - break; - case MHD_HTTP_UNAUTHORIZED: - GNUNET_break (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 */ - 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. */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != wsh->cb) - { - wsh->cb (wsh->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - NULL, - j); - wsh->cb = NULL; - } - TALER_EXCHANGE_reserve_withdraw_cancel (wsh); -} - - -/** - * Helper function for #TALER_EXCHANGE_reserve_withdraw2() and - * #TALER_EXCHANGE_reserve_withdraw(). - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param pk kind of coin to create - * @param reserve_sig signature from the reserve authorizing the withdrawal - * @param reserve_pub public key of the reserve to withdraw from - * @param ps secrets of the planchet - * caller must have committed this value to disk before the call (with @a pk) - * @param pd planchet details matching @a ps - * @param res_cb the callback to call when the final result for this request is available - * @param res_cb_cls closure for @a res_cb - * @return NULL - * if the inputs are invalid (i.e. denomination key not with this exchange). - * In this case, the callback is not called. - */ -struct TALER_EXCHANGE_ReserveWithdrawHandle * -reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_EXCHANGE_DenomPublicKey *pk, - const struct TALER_ReserveSignatureP *reserve_sig, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_PlanchetSecretsP *ps, - const struct TALER_PlanchetDetail *pd, - TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; - struct GNUNET_CURL_Context *ctx; - json_t *withdraw_obj; - CURL *eh; - - wsh = GNUNET_new (struct TALER_EXCHANGE_ReserveWithdrawHandle); - wsh->exchange = exchange; - wsh->cb = res_cb; - wsh->cb_cls = res_cb_cls; - wsh->pk = pk; - wsh->reserve_pub = *reserve_pub; - wsh->c_hash = pd->c_hash; - withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub and coin_ev */ - " s:o, s:o}",/* reserve_pub and reserve_sig */ - "denom_pub", GNUNET_JSON_from_rsa_public_key (pk->key.rsa_public_key), - "coin_ev", GNUNET_JSON_from_data (pd->coin_ev, - pd->coin_ev_size), - "reserve_pub", GNUNET_JSON_from_data_auto (reserve_pub), - "reserve_sig", GNUNET_JSON_from_data_auto (reserve_sig)); - if (NULL == withdraw_obj) - { - GNUNET_break (0); - return NULL; - } - - wsh->ps = *ps; - wsh->url = TEAH_path_to_url (exchange, "/reserve/withdraw"); - - eh = TEL_curl_easy_get (wsh->url); - GNUNET_assert (NULL != (wsh->json_enc = - json_dumps (withdraw_obj, - JSON_COMPACT))); - json_decref (withdraw_obj); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDS, - wsh->json_enc)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDSIZE, - strlen (wsh->json_enc))); - ctx = TEAH_handle_to_context (exchange); - wsh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_reserve_withdraw_finished, - wsh); - return wsh; -} - - -/** - * Withdraw a coin from the exchange using a /reserve/withdraw request. Note - * that to ensure that no money is lost in case of hardware failures, - * the caller must have committed (most of) the arguments to disk - * before calling, and be ready to repeat the request with the same - * arguments in case of failures. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param pk kind of coin to create - * @param reserve_priv private key of the reserve to withdraw from - * @param ps secrets of the planchet - * caller must have committed this value to disk before the call (with @a pk) - * @param res_cb the callback to call when the final result for this request is available - * @param res_cb_cls closure for the above callback - * @return handle for the operation on success, NULL on error, i.e. - * if the inputs are invalid (i.e. denomination key not with this exchange). - * In this case, the callback is not called. - */ -struct TALER_EXCHANGE_ReserveWithdrawHandle * -TALER_EXCHANGE_reserve_withdraw (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_EXCHANGE_DenomPublicKey *pk, - const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_PlanchetSecretsP *ps, - TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb, - void *res_cb_cls) -{ - struct TALER_Amount amount_with_fee; - struct TALER_ReserveSignatureP reserve_sig; - struct TALER_WithdrawRequestPS req; - struct TALER_PlanchetDetail pd; - struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; - - GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, - &req.reserve_pub.eddsa_pub); - req.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS)); - req.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); - if (GNUNET_OK != - TALER_amount_add (&amount_with_fee, - &pk->fee_withdraw, - &pk->value)) - { - /* exchange gave us denomination keys that overflow like this!? */ - GNUNET_break_op (0); - return NULL; - } - TALER_amount_hton (&req.amount_with_fee, - &amount_with_fee); - TALER_amount_hton (&req.withdraw_fee, - &pk->fee_withdraw); - if (GNUNET_OK != - TALER_planchet_prepare (&pk->key, - ps, - &pd)) - { - GNUNET_break_op (0); - return NULL; - } - req.h_denomination_pub = pd.denom_pub_hash; - GNUNET_CRYPTO_hash (pd.coin_ev, - pd.coin_ev_size, - &req.h_coin_envelope); - GNUNET_assert (GNUNET_OK == - GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, - &req.purpose, - &reserve_sig.eddsa_signature)); - wsh = reserve_withdraw_internal (exchange, - pk, - &reserve_sig, - &req.reserve_pub, - ps, - &pd, - res_cb, - res_cb_cls); - GNUNET_free (pd.coin_ev); - return wsh; -} - - -/** - * Withdraw a coin from the exchange using a /reserve/withdraw - * request. This API is typically used by a wallet to withdraw a tip - * where the reserve's signature was created by the merchant already. - * - * Note that to ensure that no money is lost in case of hardware - * failures, the caller must have committed (most of) the arguments to - * disk before calling, and be ready to repeat the request with the - * same arguments in case of failures. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param pk kind of coin to create - * @param reserve_sig signature from the reserve authorizing the withdrawal - * @param reserve_pub public key of the reserve to withdraw from - * @param ps secrets of the planchet - * caller must have committed this value to disk before the call (with @a pk) - * @param res_cb the callback to call when the final result for this request is available - * @param res_cb_cls closure for @a res_cb - * @return NULL - * if the inputs are invalid (i.e. denomination key not with this exchange). - * In this case, the callback is not called. - */ -struct TALER_EXCHANGE_ReserveWithdrawHandle * -TALER_EXCHANGE_reserve_withdraw2 (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_EXCHANGE_DenomPublicKey *pk, - const struct TALER_ReserveSignatureP *reserve_sig, - const struct TALER_ReservePublicKeyP *reserve_pub, - const struct TALER_PlanchetSecretsP *ps, - TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; - struct TALER_PlanchetDetail pd; - - if (GNUNET_OK != - TALER_planchet_prepare (&pk->key, - ps, - &pd)) - { - GNUNET_break_op (0); - return NULL; - } - wsh = reserve_withdraw_internal (exchange, - pk, - reserve_sig, - reserve_pub, - ps, - &pd, - res_cb, - res_cb_cls); - GNUNET_free (pd.coin_ev); - return wsh; -} - - -/** - * Cancel a withdraw status request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param sign the withdraw sign request handle - */ -void -TALER_EXCHANGE_reserve_withdraw_cancel (struct TALER_EXCHANGE_ReserveWithdrawHandle *sign) -{ - if (NULL != sign->job) - { - GNUNET_CURL_job_cancel (sign->job); - sign->job = NULL; - } - GNUNET_free (sign->url); - GNUNET_free (sign->json_enc); - GNUNET_free (sign); -} - - -/* end of exchange_api_reserve.c */ diff --git a/src/exchange-lib/exchange_api_track_transaction.c b/src/exchange-lib/exchange_api_track_transaction.c @@ -1,367 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015, 2016 GNUnet e.V. - - 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 exchange-lib/exchange_api_track_transaction.c - * @brief Implementation of the /track/transaction 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 "curl_defaults.h" - - -/** - * @brief A Deposit Wtid Handle - */ -struct TALER_EXCHANGE_TrackTransactionHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * JSON encoding of the request to POST. - */ - char *json_enc; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_TrackTransactionCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Information the exchange should sign in response. - * (with pre-filled fields from the request). - */ - struct TALER_ConfirmWirePS depconf; - -}; - - -/** - * Verify that the signature on the "200 OK" response - * from the exchange is valid. - * - * @param dwh deposit wtid handle - * @param json json reply with the signature - * @param[out] exchange_pub set to the exchange's public key - * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not - */ -static int -verify_deposit_wtid_signature_ok (const struct TALER_EXCHANGE_TrackTransactionHandle *dwh, - 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_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - key_state = TALER_EXCHANGE_get_keys (dwh->exchange); - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_state, - exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE, - &dwh->depconf.purpose, - &exchange_sig.eddsa_signature, - &exchange_pub->eddsa_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /track/transaction request. - * - * @param cls the `struct TALER_EXCHANGE_TrackTransactionHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_deposit_wtid_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_TrackTransactionHandle *dwh = cls; - const struct TALER_WireTransferIdentifierRawP *wtid = NULL; - struct GNUNET_TIME_Absolute execution_time = GNUNET_TIME_UNIT_FOREVER_ABS; - const struct TALER_Amount *coin_contribution = NULL; - struct TALER_Amount coin_contribution_s; - struct TALER_ExchangePublicKeyP exchange_pub; - struct TALER_ExchangePublicKeyP *ep = NULL; - const json_t *j = response; - - dwh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("wtid", &dwh->depconf.wtid), - GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time), - TALER_JSON_spec_amount ("coin_contribution", &coin_contribution_s), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - response_code = 0; - break; - } - wtid = &dwh->depconf.wtid; - dwh->depconf.execution_time = GNUNET_TIME_absolute_hton (execution_time); - TALER_amount_hton (&dwh->depconf.coin_contribution, - &coin_contribution_s); - coin_contribution = &coin_contribution_s; - if (GNUNET_OK != - verify_deposit_wtid_signature_ok (dwh, - j, - &exchange_pub)) - { - GNUNET_break_op (0); - response_code = 0; - } - else - { - ep = &exchange_pub; - } - } - break; - case MHD_HTTP_ACCEPTED: - { - /* Transaction known, but not executed yet */ - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - response_code = 0; - break; - } - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_UNAUTHORIZED: - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* Exchange does not know about transaction; - we should pass the reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - dwh->cb (dwh->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - ep, - j, - wtid, - execution_time, - coin_contribution); - TALER_EXCHANGE_track_transaction_cancel (dwh); -} - - -/** - * Obtain wire transfer details about an existing deposit operation. - * - * @param exchange the exchange to query - * @param merchant_priv the merchant's private key - * @param h_wire hash of merchant's wire transfer details - * @param h_contract_terms hash of the proposal data from the contract - * between merchant and customer - * @param coin_pub public key of the coin - * @param cb function to call with the result - * @param cb_cls closure for @a cb - * @return handle to abort request - */ -struct TALER_EXCHANGE_TrackTransactionHandle * -TALER_EXCHANGE_track_transaction (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_MerchantPrivateKeyP *merchant_priv, - const struct GNUNET_HashCode *h_wire, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - TALER_EXCHANGE_TrackTransactionCallback cb, - void *cb_cls) -{ - struct TALER_DepositTrackPS dtp; - struct TALER_MerchantSignatureP merchant_sig; - struct TALER_EXCHANGE_TrackTransactionHandle *dwh; - struct GNUNET_CURL_Context *ctx; - json_t *deposit_wtid_obj; - CURL *eh; - - if (GNUNET_YES != - TEAH_handle_is_ready (exchange)) - { - GNUNET_break (0); - return NULL; - } - dtp.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION); - dtp.purpose.size = htonl (sizeof (dtp)); - dtp.h_contract_terms = *h_contract_terms; - dtp.h_wire = *h_wire; - GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, - &dtp.merchant.eddsa_pub); - - dtp.coin_pub = *coin_pub; - GNUNET_assert (GNUNET_OK == - GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv, - &dtp.purpose, - &merchant_sig.eddsa_sig)); - deposit_wtid_obj = json_pack ("{s:o, s:o," /* H_wire, h_contract_terms */ - " s:o," /* coin_pub */ - " s:o, s:o}", /* merchant_pub, merchant_sig */ - "H_wire", GNUNET_JSON_from_data_auto (h_wire), - "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms), - "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), - "merchant_pub", GNUNET_JSON_from_data_auto (&dtp.merchant), - "merchant_sig", GNUNET_JSON_from_data_auto (&merchant_sig)); - if (NULL == deposit_wtid_obj) - { - GNUNET_break (0); - return NULL; - } - - dwh = GNUNET_new (struct TALER_EXCHANGE_TrackTransactionHandle); - dwh->exchange = exchange; - dwh->cb = cb; - dwh->cb_cls = cb_cls; - dwh->url = TEAH_path_to_url (exchange, "/track/transaction"); - dwh->depconf.purpose.size = htonl (sizeof (struct TALER_ConfirmWirePS)); - dwh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE); - dwh->depconf.h_wire = *h_wire; - dwh->depconf.h_contract_terms = *h_contract_terms; - dwh->depconf.coin_pub = *coin_pub; - - eh = TEL_curl_easy_get (dwh->url); - GNUNET_assert (NULL != (dwh->json_enc = - json_dumps (deposit_wtid_obj, - JSON_COMPACT))); - json_decref (deposit_wtid_obj); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDS, - dwh->json_enc)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDSIZE, - strlen (dwh->json_enc))); - ctx = TEAH_handle_to_context (exchange); - dwh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_deposit_wtid_finished, - dwh); - return dwh; -} - - -/** - * Cancel deposit wtid request. This function cannot be used on a request - * handle if a response is already served for it. - * - * @param dwh the wire deposits request handle - */ -void -TALER_EXCHANGE_track_transaction_cancel (struct TALER_EXCHANGE_TrackTransactionHandle *dwh) -{ - if (NULL != dwh->job) - { - GNUNET_CURL_job_cancel (dwh->job); - dwh->job = NULL; - } - GNUNET_free (dwh->url); - GNUNET_free (dwh->json_enc); - GNUNET_free (dwh); -} - - -/* end of exchange_api_deposit_wtid.c */ diff --git a/src/exchange-lib/exchange_api_track_transfer.c b/src/exchange-lib/exchange_api_track_transfer.c @@ -1,388 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015, 2016 GNUnet e.V. - - 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 exchange-lib/exchange_api_track_transfer.c - * @brief Implementation of the /track/transfer 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_curl_lib.h> -#include "taler_exchange_service.h" -#include "taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler_signatures.h" -#include "curl_defaults.h" - - -/** - * @brief A /track/transfer Handle - */ -struct TALER_EXCHANGE_TrackTransferHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_TrackTransferCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * We got a #MHD_HTTP_OK response for the /track/transfer request. - * Check that the response is well-formed and if it is, call the - * callback. If not, return an error code. - * - * This code is very similar to - * merchant_api_track_transfer.c::check_track_transfer_response_ok. - * Any changes should likely be reflected there as well. - * - * @param wdh handle to the operation - * @param json response we got - * @return #GNUNET_OK if we are done and all is well, - * #GNUNET_SYSERR if the response was bogus - */ -static int -check_track_transfer_response_ok (struct TALER_EXCHANGE_TrackTransferHandle *wdh, - const json_t *json) -{ - json_t *details_j; - struct GNUNET_HashCode h_wire; - struct GNUNET_TIME_Absolute exec_time; - struct TALER_Amount total_amount; - struct TALER_Amount total_expected; - struct TALER_Amount wire_fee; - struct TALER_MerchantPublicKeyP merchant_pub; - unsigned int num_details; - struct TALER_ExchangePublicKeyP exchange_pub; - struct TALER_ExchangeSignatureP exchange_sig; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount ("total", &total_amount), - TALER_JSON_spec_amount ("wire_fee", &wire_fee), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub), - GNUNET_JSON_spec_fixed_auto ("H_wire", &h_wire), - GNUNET_JSON_spec_absolute_time ("execution_time", &exec_time), - GNUNET_JSON_spec_json ("deposits", &details_j), - 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_amount_get_zero (total_amount.currency, - &total_expected)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - num_details = json_array_size (details_j); - { - struct TALER_TrackTransferDetails details[num_details]; - unsigned int i; - struct GNUNET_HashContext *hash_context; - struct TALER_WireDepositDetailP dd; - struct TALER_WireDepositDataPS wdp; - - hash_context = GNUNET_CRYPTO_hash_context_start (); - for (i=0;i<num_details;i++) - { - struct TALER_TrackTransferDetails *detail = &details[i]; - struct json_t *detail_j = json_array_get (details_j, i); - struct GNUNET_JSON_Specification spec_detail[] = { - GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &detail->h_contract_terms), - GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub), - TALER_JSON_spec_amount ("deposit_value", &detail->coin_value), - TALER_JSON_spec_amount ("deposit_fee", &detail->coin_fee), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (detail_j, - spec_detail, - NULL, NULL)) - { - GNUNET_break_op (0); - GNUNET_CRYPTO_hash_context_abort (hash_context); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - /* build up big hash for signature checking later */ - dd.h_contract_terms = detail->h_contract_terms; - dd.execution_time = GNUNET_TIME_absolute_hton (exec_time); - dd.coin_pub = detail->coin_pub; - TALER_amount_hton (&dd.deposit_value, - &detail->coin_value); - TALER_amount_hton (&dd.deposit_fee, - &detail->coin_fee); - if ( (GNUNET_OK != - TALER_amount_add (&total_expected, - &total_expected, - &detail->coin_value)) || - (GNUNET_OK != - TALER_amount_subtract (&total_expected, - &total_expected, - &detail->coin_fee)) ) - { - GNUNET_break_op (0); - GNUNET_CRYPTO_hash_context_abort (hash_context); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - GNUNET_CRYPTO_hash_context_read (hash_context, - &dd, - sizeof (struct TALER_WireDepositDetailP)); - } - /* Check signature */ - wdp.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT); - wdp.purpose.size = htonl (sizeof (struct TALER_WireDepositDataPS)); - TALER_amount_hton (&wdp.total, - &total_amount); - TALER_amount_hton (&wdp.wire_fee, - &wire_fee); - wdp.merchant_pub = merchant_pub; - wdp.h_wire = h_wire; - GNUNET_CRYPTO_hash_context_finish (hash_context, - &wdp.h_details); - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (TALER_EXCHANGE_get_keys (wdh->exchange), - &exchange_pub)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify - (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT, - &wdp.purpose, - &exchange_sig.eddsa_signature, - &exchange_pub.eddsa_pub)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - - if (GNUNET_OK != - TALER_amount_subtract (&total_expected, - &total_expected, - &wire_fee)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - if (0 != - TALER_amount_cmp (&total_expected, - &total_amount)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - wdh->cb (wdh->cb_cls, - MHD_HTTP_OK, - TALER_EC_NONE, - &exchange_pub, - json, - &h_wire, - exec_time, - &total_amount, - &wire_fee, - num_details, - details); - } - GNUNET_JSON_parse_free (spec); - TALER_EXCHANGE_track_transfer_cancel (wdh); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /track/transfer request. - * - * @param cls the `struct TALER_EXCHANGE_TrackTransferHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_track_transfer_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_TrackTransferHandle *wdh = cls; - const json_t *j = response; - - wdh->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - if (GNUNET_OK == - check_track_transfer_response_ok (wdh, - j)) - return; - GNUNET_break_op (0); - response_code = 0; - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_UNAUTHORIZED: - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - /* Exchange does not know about transaction; - we should pass the reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - wdh->cb (wdh->cb_cls, - response_code, - TALER_JSON_get_error_code (j), - NULL, - j, - NULL, - GNUNET_TIME_UNIT_ZERO_ABS, - NULL, - NULL, - 0, NULL); - TALER_EXCHANGE_track_transfer_cancel (wdh); -} - - -/** - * Query the exchange about which transactions were combined - * to create a wire transfer. - * - * @param exchange exchange to query - * @param wtid raw wire transfer identifier to get information about - * @param cb callback to call - * @param cb_cls closure for @a cb - * @return handle to cancel operation - */ -struct TALER_EXCHANGE_TrackTransferHandle * -TALER_EXCHANGE_track_transfer (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_WireTransferIdentifierRawP *wtid, - TALER_EXCHANGE_TrackTransferCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_TrackTransferHandle *wdh; - struct GNUNET_CURL_Context *ctx; - char *buf; - char *path; - CURL *eh; - - if (GNUNET_YES != - TEAH_handle_is_ready (exchange)) - { - GNUNET_break (0); - return NULL; - } - - wdh = GNUNET_new (struct TALER_EXCHANGE_TrackTransferHandle); - wdh->exchange = exchange; - wdh->cb = cb; - wdh->cb_cls = cb_cls; - - buf = GNUNET_STRINGS_data_to_string_alloc (wtid, - sizeof (struct TALER_WireTransferIdentifierRawP)); - GNUNET_asprintf (&path, - "/track/transfer?wtid=%s", - buf); - wdh->url = TEAH_path_to_url (wdh->exchange, - path); - GNUNET_free (buf); - GNUNET_free (path); - - eh = TEL_curl_easy_get (wdh->url); - ctx = TEAH_handle_to_context (exchange); - wdh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_track_transfer_finished, - wdh); - return wdh; -} - - -/** - * Cancel wire deposits request. This function cannot be used on a request - * handle if a response is already served for it. - * - * @param wdh the wire deposits request handle - */ -void -TALER_EXCHANGE_track_transfer_cancel (struct TALER_EXCHANGE_TrackTransferHandle *wdh) -{ - if (NULL != wdh->job) - { - GNUNET_CURL_job_cancel (wdh->job); - wdh->job = NULL; - } - GNUNET_free (wdh->url); - GNUNET_free (wdh); -} - - -/* end of exchange_api_wire_deposits.c */ diff --git a/src/exchange-lib/exchange_api_wire.c b/src/exchange-lib/exchange_api_wire.c @@ -1,442 +0,0 @@ -/* - 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 exchange-lib/exchange_api_wire.c - * @brief Implementation of the /wire 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_curl_lib.h> -#include "taler_exchange_service.h" -#include "taler_json_lib.h" -#include "taler_wire_lib.h" -#include "taler_signatures.h" -#include "taler_wire_plugin.h" -#include "exchange_api_handle.h" -#include "curl_defaults.h" - - -/** - * @brief A Wire Handle - */ -struct TALER_EXCHANGE_WireHandle -{ - - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_WireResultCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * List of wire fees by method. - */ -struct FeeMap -{ - /** - * Next entry in list. - */ - struct FeeMap *next; - - /** - * Wire method this fee structure is for. - */ - char *method; - - /** - * Array of wire fees, also linked list, but allocated - * only once. - */ - struct TALER_EXCHANGE_WireAggregateFees *fee_list; -}; - - -/** - * Frees @a fm. - * - * @param fm memory to release - */ -static void -free_fees (struct FeeMap *fm) -{ - while (NULL != fm) - { - struct FeeMap *fe = fm->next; - - GNUNET_free (fm->fee_list); - GNUNET_free (fm->method); - GNUNET_free (fm); - fm = fe; - } -} - - -/** - * Parse wire @a fees and return map. - * - * @param fees json AggregateTransferFee to parse - * @return NULL on error - */ -static struct FeeMap * -parse_fees (json_t *fees) -{ - struct FeeMap *fm = NULL; - const char *key; - json_t *fee_array; - - json_object_foreach (fees, key, fee_array) { - struct FeeMap *fe = GNUNET_new (struct FeeMap); - int len; - unsigned int idx; - json_t *fee; - - if (0 == (len = json_array_size (fee_array))) - { - GNUNET_break_op (0); - GNUNET_free (fe); - continue; /* skip */ - } - fe->method = GNUNET_strdup (key); - fe->next = fm; - fe->fee_list = GNUNET_new_array (len, - struct TALER_EXCHANGE_WireAggregateFees); - fm = fe; - json_array_foreach (fee_array, idx, fee) - { - struct TALER_EXCHANGE_WireAggregateFees *wa = &fe->fee_list[idx]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("sig", - &wa->master_sig), - TALER_JSON_spec_amount ("wire_fee", - &wa->wire_fee), - TALER_JSON_spec_amount ("closing_fee", - &wa->closing_fee), - GNUNET_JSON_spec_absolute_time ("start_date", - &wa->start_date), - GNUNET_JSON_spec_absolute_time ("end_date", - &wa->end_date), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (fee, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - free_fees (fm); - return NULL; - } - if (idx + 1 < len) - wa->next = &fe->fee_list[idx + 1]; - else - wa->next = NULL; - } - } - return fm; -} - - -/** - * Find fee by @a method. - * - * @param fm map to look in - * @param method key to look for - * @return NULL if fee is not specified in @a fm - */ -static const struct TALER_EXCHANGE_WireAggregateFees * -lookup_fee (const struct FeeMap *fm, - const char *method) -{ - for (;NULL != fm; fm = fm->next) - if (0 == strcasecmp (fm->method, - method)) - return fm->fee_list; - return NULL; -} - - -/** - * Function called when we're done processing the - * HTTP /wire request. - * - * @param cls the `struct TALER_EXCHANGE_WireHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_wire_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_WireHandle *wh = cls; - enum TALER_ErrorCode ec; - const json_t *j = response; - - TALER_LOG_DEBUG ("Checking raw /wire response\n"); - wh->job = NULL; - ec = TALER_EC_NONE; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_OK: - { - json_t *accounts; - json_t *fees; - int num_accounts; - struct FeeMap *fm; - const struct TALER_EXCHANGE_Keys *key_state; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("accounts", &accounts), - GNUNET_JSON_spec_json ("fees", &fees), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - /* bogus reply */ - GNUNET_break_op (0); - response_code = 0; - ec = TALER_EC_SERVER_JSON_INVALID; - break; - } - if (0 == (num_accounts = json_array_size (accounts))) - { - /* bogus reply */ - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - response_code = 0; - ec = TALER_EC_SERVER_JSON_INVALID; - break; - } - if (NULL == (fm = parse_fees (fees))) - { - /* bogus reply */ - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - response_code = 0; - ec = TALER_EC_SERVER_JSON_INVALID; - break; - } - - key_state = TALER_EXCHANGE_get_keys (wh->exchange); - /* parse accounts */ - { - struct TALER_EXCHANGE_WireAccount was[num_accounts]; - - for (unsigned int i=0;i<num_accounts;i++) - { - struct TALER_EXCHANGE_WireAccount *wa = &was[i]; - json_t *account; - struct GNUNET_JSON_Specification spec_account[] = { - GNUNET_JSON_spec_string ("url", &wa->url), - GNUNET_JSON_spec_fixed_auto ("master_sig", &wa->master_sig), - GNUNET_JSON_spec_end() - }; - char *method; - - account = json_array_get (accounts, - i); - if (GNUNET_OK != - TALER_JSON_exchange_wire_signature_check (account, - &key_state->master_pub)) - { - /* bogus reply */ - GNUNET_break_op (0); - response_code = 0; - ec = TALER_EC_SERVER_SIGNATURE_INVALID; - break; - } - if (GNUNET_OK != - GNUNET_JSON_parse (account, - spec_account, - NULL, NULL)) - { - /* bogus reply */ - GNUNET_break_op (0); - response_code = 0; - ec = TALER_EC_SERVER_JSON_INVALID; - break; - } - if (NULL == (method = TALER_WIRE_payto_get_method (wa->url))) - { - /* bogus reply */ - GNUNET_break_op (0); - response_code = 0; - ec = TALER_EC_SERVER_JSON_INVALID; - break; - } - if (NULL == (wa->fees = lookup_fee (fm, - method))) - { - /* bogus reply */ - GNUNET_break_op (0); - response_code = 0; - ec = TALER_EC_SERVER_JSON_INVALID; - GNUNET_free (method); - break; - } - GNUNET_free (method); - } /* end 'for all accounts */ - if ( (0 != response_code) && - (NULL != wh->cb) ) - { - wh->cb (wh->cb_cls, - response_code, - TALER_EC_NONE, - num_accounts, - was); - wh->cb = NULL; - } - } /* end of 'parse accounts */ - free_fees (fm); - GNUNET_JSON_parse_free (spec); - } /* end of MHD_HTTP_OK */ - 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_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u\n", - (unsigned int) response_code); - GNUNET_break (0); - response_code = 0; - break; - } - if (NULL != wh->cb) - wh->cb (wh->cb_cls, - response_code, - (0 == response_code) ? ec : TALER_JSON_get_error_code (j), - 0, - NULL); - TALER_EXCHANGE_wire_cancel (wh); -} - - -/** - * Obtain information about a exchange's wire instructions. - * A exchange may provide wire instructions for creating - * a reserve. The wire instructions also indicate - * which wire formats merchants may use with the exchange. - * This API is typically used by a wallet for wiring - * funds, and possibly by a merchant to determine - * supported wire formats. - * - * Note that while we return the (main) response verbatim to the - * caller for further processing, we do already verify that the - * response is well-formed (i.e. that signatures included in the - * response are all valid). If the exchange's reply is not well-formed, - * we return an HTTP status code of zero to @a cb. - * - * @param exchange the exchange handle; the exchange must be ready to operate - * @param wire_cb the callback to call when a reply for this request is available - * @param wire_cb_cls closure for the above callback - * @return a handle for this request - */ -struct TALER_EXCHANGE_WireHandle * -TALER_EXCHANGE_wire (struct TALER_EXCHANGE_Handle *exchange, - TALER_EXCHANGE_WireResultCallback wire_cb, - void *wire_cb_cls) -{ - struct TALER_EXCHANGE_WireHandle *wh; - struct GNUNET_CURL_Context *ctx; - CURL *eh; - - if (GNUNET_YES != - TEAH_handle_is_ready (exchange)) - { - GNUNET_break (0); - return NULL; - } - wh = GNUNET_new (struct TALER_EXCHANGE_WireHandle); - wh->exchange = exchange; - wh->cb = wire_cb; - wh->cb_cls = wire_cb_cls; - wh->url = TEAH_path_to_url (exchange, "/wire"); - - eh = TEL_curl_easy_get (wh->url); - ctx = TEAH_handle_to_context (exchange); - wh->job = GNUNET_CURL_job_add (ctx, - eh, - GNUNET_YES, - &handle_wire_finished, - wh); - return wh; -} - - -/** - * Cancel a wire information request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param wh the wire information request handle - */ -void -TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh) -{ - if (NULL != wh->job) - { - GNUNET_CURL_job_cancel (wh->job); - wh->job = NULL; - } - GNUNET_free (wh->url); - GNUNET_free (wh); -} - - -/* end of exchange_api_wire.c */ diff --git a/src/exchange-lib/test_exchange_api_home/.config/taler/account-1.json b/src/exchange-lib/test_exchange_api_home/.config/taler/account-1.json @@ -1,5 +0,0 @@ -{ - "url": "payto://sepa/CH9300762011623852957", - "salt": "N83T9J9202WCC8TQFDMJDWEGZNBEKA33C1ZM241VNYH88RZNTHPW509Y1M2YF7Y098R8VRESWQ05H03BK1SPAZCWE54KARDCKT5N8AG", - "master_sig": "D4V5GJ998YK7D6N0N56AD0J6MZNFEW6MRZT2CFPVQ5ME3NMQ59AA2007CXYESSFGRN70CNCFM06858QSSENCWTZM8VHEJ93YQ20ZJ1R" -} -\ No newline at end of file diff --git a/src/exchange-lib/test_exchange_api_home/.config/taler/account-2.json b/src/exchange-lib/test_exchange_api_home/.config/taler/account-2.json @@ -1,4 +0,0 @@ -{ - "url": "payto://x-taler-bank/localhost:8082/2", - "master_sig": "HC47BZN3C0KJ2VPMJ5EJWD2FXJ72AET0NWFE6JGSGK5CXS4GSKJJ6Z7BTS56JWM7B40SD61Z5GYYMRRE3X9JTJBVMWE0X7XHNXQ9P38" -} -\ No newline at end of file diff --git a/src/exchange-lib/test_exchange_api_home/.config/taler/sepa.json b/src/exchange-lib/test_exchange_api_home/.config/taler/sepa.json @@ -1,9 +0,0 @@ -{ - "name": "Max Musterman", - "bic": "COBADEFF370", - "type": "sepa", - "sig": "4EVRC2MCJPXQC8MC00831DNWEXMZAP4JQDDE1A7R6KR3MANG24RC1VQ55AX5A2E35S58VW1VSTENFTPHG5MWG9BSN8B8WXSV21KKW20", - "address": "Musterstadt", - "salt": "3KTM1ZRMWGEQPQ254S4R5R4Q8XM0ZYWTCTE01TZ76MVBSQ6RX7A5DR08WXVH1DCHR1R7ACRB7X0EVC2XDW1CBZM9WFSD9TRMZ90BR98", - "iban": "DE89370400440532013000" -} -\ No newline at end of file diff --git a/src/exchange-lib/test_exchange_api_home/.config/taler/test.json b/src/exchange-lib/test_exchange_api_home/.config/taler/test.json @@ -1,8 +0,0 @@ -{ - "salt": "AZPRFVJ58NM6M7J5CZQPJAH3EW5DYM52AEZ9Y1C1ER3W94QV8D8TQKF6CK8MYQRA9QMSKDQTGZ306ZS9GQ0M6R01CJ20KPP49WFDZK8", - "name": "The exchange", - "account_number": 3, - "bank_url": "http://localhost:8082/", - "type": "test", - "sig": "RPQXP9S4P8PQP7HEZQNRSZCT0ATNEP8GW0P5TPM34V5RX86FCD670V44R9NETSYDDKB8SZV7TKY9PAJYTY51D3VDWY9XXQ5BPFRXR28" -} diff --git a/src/exchange-lib/test_exchange_api_home/.config/taler/x-taler-bank.json b/src/exchange-lib/test_exchange_api_home/.config/taler/x-taler-bank.json @@ -1,4 +0,0 @@ -{ - "url": "payto://x-taler-bank/http://localhost:8082/2", - "master_sig": "KQ0BWSCNVR7HGGSAMCYK8ZM30RBS1MHMXT3QBN01PZWC9TV72FEE5RJ7T84C8134EPV6WEBXXY2MTFNE8ZXST6JEJQKR8HX6FQPVY10" -} -\ No newline at end of file diff --git a/src/exchange-lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv b/src/exchange-lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv @@ -1 +0,0 @@ -pÚ^ó-Ú33€XXÁ!\0qúýŤÂµmUţ_‰ -\ No newline at end of file diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am @@ -0,0 +1,255 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/bank-lib + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = \ + libtalerexchange.la \ + libtalertesting.la \ + libtalerauditor.la \ + libtalerauditortesting.la + +libtalerexchange_la_LDFLAGS = \ + -version-info 4:0:0 \ + -no-undefined +libtalerexchange_la_SOURCES = \ + exchange_api_curl_defaults.c exchange_api_curl_defaults.h \ + exchange_api_common.c \ + exchange_api_handle.c exchange_api_handle.h \ + exchange_api_deposit.c \ + exchange_api_payback.c \ + exchange_api_refresh.c \ + exchange_api_refresh_link.c \ + exchange_api_refund.c \ + exchange_api_reserve.c \ + exchange_api_track_transaction.c \ + exchange_api_track_transfer.c \ + exchange_api_wire.c +libtalerexchange_la_LIBADD = \ + libtalerauditor.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetcurl \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + $(XLIB) + + +libtalerauditor_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined +libtalerauditor_la_SOURCES = \ + auditor_api_curl_defaults.c auditor_api_curl_defaults.h \ + auditor_api_handle.c auditor_api_handle.h \ + auditor_api_deposit_confirmation.c \ + auditor_api_exchanges.c +libtalerauditor_la_LIBADD = \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetcurl \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + $(XLIB) + +if HAVE_LIBCURL +libtalerauditor_la_LIBADD += -lcurl +else +if HAVE_LIBGNURL +libtalerauditor_la_LIBADD += -lgnurl +endif +endif + + +libtalertesting_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined +libtalertesting_la_SOURCES = \ + exchange_api_curl_defaults.c \ + testing_api_cmd_exec_aggregator.c \ + testing_api_cmd_exec_wirewatch.c \ + testing_api_cmd_exec_keyup.c \ + testing_api_cmd_exec_auditor-sign.c \ + testing_api_cmd_fakebank_transfer.c \ + testing_api_cmd_withdraw.c \ + testing_api_cmd_wire.c \ + testing_api_cmd_refund.c \ + testing_api_cmd_status.c \ + testing_api_cmd_deposit.c \ + testing_api_cmd_sleep.c \ + testing_api_cmd_refresh.c \ + testing_api_cmd_track.c \ + testing_api_cmd_bank_check.c \ + testing_api_cmd_payback.c \ + testing_api_cmd_signal.c \ + testing_api_cmd_check_keys.c \ + testing_api_cmd_batch.c \ + testing_api_cmd_serialize_keys.c \ + testing_api_helpers.c \ + testing_api_loop.c \ + testing_api_traits.c \ + testing_api_trait_blinding_key.c \ + testing_api_trait_coin_priv.c \ + testing_api_trait_denom_pub.c \ + testing_api_trait_denom_sig.c \ + testing_api_trait_exchange_pub.c \ + testing_api_trait_exchange_sig.c \ + testing_api_trait_json.c \ + testing_api_trait_process.c \ + testing_api_trait_reserve_priv.c \ + testing_api_trait_number.c \ + testing_api_trait_fresh_coin.c \ + testing_api_trait_string.c \ + testing_api_trait_key_peer.c \ + testing_api_trait_wtid.c \ + testing_api_trait_amount.c \ + testing_api_trait_cmd.c +libtalertesting_la_LIBADD = \ + libtalerexchange.la \ + $(top_builddir)/src/wire/libtalerwire.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + $(top_builddir)/src/bank-lib/libtalerfakebank.la \ + -lgnunetcurl \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + $(XLIB) + +libtalerauditortesting_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined +libtalerauditortesting_la_SOURCES = \ + testing_auditor_api_helpers.c \ + testing_auditor_api_cmd_deposit_confirmation.c \ + testing_auditor_api_cmd_exchanges.c \ + testing_auditor_api_cmd_exec_auditor.c \ + testing_auditor_api_cmd_exec_auditor_dbinit.c \ + testing_auditor_api_cmd_exec_wire_auditor.c +libtalerauditortesting_la_LIBADD = \ + libtalerauditor.la \ + libtalerexchange.la \ + libtalertesting.la \ + $(top_builddir)/src/wire/libtalerwire.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetcurl \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + $(XLIB) + + +if HAVE_LIBCURL +libtalerexchange_la_LIBADD += -lcurl +else +if HAVE_LIBGNURL +libtalerexchange_la_LIBADD += -lgnurl +endif +endif + +check_PROGRAMS = \ + test_exchange_api_keys_cherry_picking_new \ + test_exchange_api_overlapping_keys_bug \ + test_exchange_api_new \ + test_auditor_api + +if HAVE_TWISTER + check_PROGRAMS += \ + test_exchange_api_twisted + +test_exchange_api_twisted_SOURCES = \ + test_exchange_api_twisted.c +test_exchange_api_twisted_LDADD = \ + $(LIBGCRYPT_LIBS) \ + libtalertesting.la \ + libtalerexchange.la \ + $(top_builddir)/src/bank-lib/libtalerfakebank.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -ltalertwistertesting \ + -lgnunetjson \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + +endif + +AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; + +TESTS = \ + $(check_PROGRAMS) + +test_exchange_api_new_SOURCES = \ + test_exchange_api_new.c +test_exchange_api_new_LDADD = \ + libtalertesting.la \ + libtalerexchange.la \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/bank-lib/libtalerfakebank.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + +test_exchange_api_overlapping_keys_bug_SOURCES = \ + test_exchange_api_overlapping_keys_bug.c +test_exchange_api_overlapping_keys_bug_LDADD = \ + libtalertesting.la \ + libtalerexchange.la \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + +test_exchange_api_keys_cherry_picking_new_SOURCES = \ + test_exchange_api_keys_cherry_picking_new.c +test_exchange_api_keys_cherry_picking_new_LDADD = \ + libtalertesting.la \ + libtalerexchange.la \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + +test_auditor_api_SOURCES = \ + test_auditor_api.c +test_auditor_api_LDADD = \ + libtalerauditortesting.la \ + libtalerauditor.la \ + libtalertesting.la \ + libtalerexchange.la \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/bank-lib/libtalerfakebank.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + + + +EXTRA_DIST = \ + test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv \ + test_exchange_api_home/.config/taler/test.json \ + test_exchange_api_home/.config/taler/sepa.json \ + test_exchange_api.conf \ + test_exchange_api_keys_cherry_picking.conf \ + test_exchange_api_keys_cherry_picking_extended.conf \ + test_auditor_api.conf \ + test_auditor_api_expire_reserve_now.conf diff --git a/src/exchange-lib/afl-generate.sh b/src/lib/afl-generate.sh diff --git a/src/lib/auditor_api_curl_defaults.c b/src/lib/auditor_api_curl_defaults.c @@ -0,0 +1,75 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + 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 auditor-lib/curl_defaults.c + * @brief curl easy handle defaults + * @author Florian Dold + */ + +#include "auditor_api_curl_defaults.h" + + +/** + * Get a curl handle with the right defaults + * for the exchange lib. In the future, we might manage a pool of connections here. + * + * @param url URL to query + */ +CURL * +TAL_curl_easy_get (const char *url) +{ + CURL *eh; + + eh = curl_easy_init (); + + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_ENCODING, + "deflate")); +#ifdef CURLOPT_TCP_FASTOPEN + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TCP_FASTOPEN, + 1L)); +#endif + { + /* Unfortunately libcurl needs chunk to be alive until after + curl_easy_perform. To avoid manual cleanup, we keep + one static list here. */ + static struct curl_slist *chunk = NULL; + if (NULL == chunk) + { + /* With POST requests, we do not want to wait for the + "100 Continue" response, as our request bodies are usually + small and directy sending them saves us a round trip. + + Clearing the expect header like this disables libcurl's + default processing of the header. + + Disabling this header is safe for other HTTP methods, thus + we don't distinguish further before setting it. */ + chunk = curl_slist_append (chunk, "Expect:"); + } + GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HTTPHEADER, chunk)); + } + + return eh; +} diff --git a/src/auditor-lib/curl_defaults.h b/src/lib/auditor_api_curl_defaults.h diff --git a/src/lib/auditor_api_deposit_confirmation.c b/src/lib/auditor_api_deposit_confirmation.c @@ -0,0 +1,384 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + 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 auditor-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_json_lib.h" +#include "taler_auditor_service.h" +#include "auditor_api_handle.h" +#include "taler_signatures.h" +#include "auditor_api_curl_defaults.h" + + +/** + * @brief A DepositConfirmation Handle + */ +struct TALER_AUDITOR_DepositConfirmationHandle +{ + + /** + * The connection to auditor this request handle will use + */ + struct TALER_AUDITOR_Handle *auditor; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * 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; + + dh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + break; + 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 */ + break; + case MHD_HTTP_UNAUTHORIZED: + /* 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: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + dh->cb (dh->cb_cls, + response_code, + TALER_JSON_get_error_code (json), + json); + TALER_AUDITOR_deposit_confirmation_cancel (dh); +} + + +/** + * Verify signature information about the deposit-confirmation. + * + * @param h_wire hash of merchant wire details + * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor) + * @param timestamp timestamp when the contract was finalized, must not be too far in the future + * @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 int +verify_signatures (const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract_terms, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund_deadline, + const struct TALER_Amount *amount_without_fee, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + 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_Absolute ep_start, + struct GNUNET_TIME_Absolute ep_expire, + struct GNUNET_TIME_Absolute ep_end, + const struct TALER_MasterSignatureP *master_sig) +{ + struct TALER_DepositConfirmationPS dc; + struct TALER_ExchangeSigningKeyValidityPS sv; + + dc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT); + dc.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)); + dc.h_contract_terms = *h_contract_terms; + dc.h_wire = *h_wire; + dc.timestamp = GNUNET_TIME_absolute_hton (timestamp); + dc.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); + TALER_amount_hton (&dc.amount_without_fee, + amount_without_fee); + dc.coin_pub = *coin_pub; + dc.merchant = *merchant_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT, + &dc.purpose, + &exchange_sig->eddsa_signature, + &exchange_pub->eddsa_pub)) + { + 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; + } + sv.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY); + sv.purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS)); + sv.master_public_key = *master_pub; + sv.start = GNUNET_TIME_absolute_hton (ep_start); + sv.expire = GNUNET_TIME_absolute_hton (ep_expire); + sv.end = GNUNET_TIME_absolute_hton (ep_end); + sv.signkey_pub = *exchange_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY, + &sv.purpose, + &master_sig->eddsa_signature, + &master_pub->eddsa_pub)) + { + GNUNET_break (0); + TALER_LOG_WARNING ("Invalid signature on exchange signing key!\n"); + return GNUNET_SYSERR; + } + if (0 == GNUNET_TIME_absolute_get_remaining (ep_end).rel_value_us) + { + GNUNET_break (0); + TALER_LOG_WARNING ("Exchange signing key is no longer valid!\n"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Submit a deposit-confirmation permission to the auditor and get the + * auditor's response. Note that while we return the response + * verbatim to the caller for further processing, we do already verify + * that the response is well-formed. If the auditor's reply is not + * well-formed, we return an HTTP status code of zero to @a cb. + * + * We also verify that the @a exchange_sig is valid for this deposit-confirmation + * request, and that the @a master_sig is a valid signature for @a + * exchange_pub. Also, the @a auditor must be ready to operate (i.e. have + * finished processing the /version reply). If either check fails, we do + * NOT initiate the transaction with the auditor and instead return NULL. + * + * @param auditor the auditor handle; the auditor must be ready to operate + * @param h_wire hash of merchant wire details + * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor) + * @param timestamp timestamp when the contract was finalized, must not be too far in the future + * @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 + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + * signatures fail to verify). In this case, the callback is not called. + */ +struct TALER_AUDITOR_DepositConfirmationHandle * +TALER_AUDITOR_deposit_confirmation (struct TALER_AUDITOR_Handle *auditor, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract_terms, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund_deadline, + const struct TALER_Amount *amount_without_fee, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + 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_Absolute ep_start, + struct GNUNET_TIME_Absolute ep_expire, + struct GNUNET_TIME_Absolute ep_end, + const struct TALER_MasterSignatureP *master_sig, + TALER_AUDITOR_DepositConfirmationResultCallback cb, + void *cb_cls) +{ + struct TALER_AUDITOR_DepositConfirmationHandle *dh; + struct GNUNET_CURL_Context *ctx; + json_t *deposit_confirmation_obj; + CURL *eh; + + (void) GNUNET_TIME_round_abs (&timestamp); + (void) GNUNET_TIME_round_abs (&refund_deadline); + (void) GNUNET_TIME_round_abs (&ep_start); + (void) GNUNET_TIME_round_abs (&ep_expire); + (void) GNUNET_TIME_round_abs (&ep_end); + GNUNET_assert (GNUNET_YES == + MAH_handle_is_ready (auditor)); + if (GNUNET_OK != + verify_signatures (h_wire, + h_contract_terms, + timestamp, + refund_deadline, + amount_without_fee, + coin_pub, + merchant_pub, + exchange_pub, + exchange_sig, + master_pub, + ep_start, + ep_expire, + ep_end, + master_sig)) + { + GNUNET_break_op (0); + return NULL; + } + + deposit_confirmation_obj + = json_pack ("{s:o, s:o," /* H_wire, h_contract_terms */ + " s:o, s:o," /* timestamp, refund_deadline */ + " s:o, s:o," /* amount_without_fees, coin_pub */ + " s:o, s:o," /* merchant_pub, exchange_sig */ + " s:o, s:o," /* master_pub, ep_start */ + " s:o, s:o," /* ep_expire, ep_end */ + " s:o}", /* master_sig */ + "H_wire", GNUNET_JSON_from_data_auto (&h_wire), + "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms), + "timestamp", GNUNET_JSON_from_time_abs (timestamp), + "refund_deadline", GNUNET_JSON_from_time_abs (refund_deadline), + "amount_without_fee", TALER_JSON_from_amount (amount_without_fee), + "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), + "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub), + "exchange_sig", GNUNET_JSON_from_data_auto (exchange_sig), + "master_pub", GNUNET_JSON_from_data_auto (master_pub), + "ep_start", GNUNET_JSON_from_time_abs (ep_start), + "ep_expire", GNUNET_JSON_from_time_abs (ep_expire), + "ep_end", GNUNET_JSON_from_time_abs (ep_end), + "master_sig", GNUNET_JSON_from_data_auto (master_sig)); + if (NULL == deposit_confirmation_obj) + { + GNUNET_break (0); + return NULL; + } + + dh = GNUNET_new (struct TALER_AUDITOR_DepositConfirmationHandle); + dh->auditor = auditor; + dh->cb = cb; + dh->cb_cls = cb_cls; + dh->url = MAH_path_to_url (auditor, "/deposit-confirmation"); + + eh = TAL_curl_easy_get (dh->url); + GNUNET_assert (NULL != (dh->json_enc = + json_dumps (deposit_confirmation_obj, + JSON_COMPACT))); + json_decref (deposit_confirmation_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for deposit-confirmation: `%s'\n", + dh->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + dh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (dh->json_enc))); + ctx = MAH_handle_to_context (auditor); + dh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_deposit_confirmation_finished, + dh); + return dh; +} + + +/** + * Cancel a deposit-confirmation permission request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param deposit-confirmation the deposit-confirmation permission request handle + */ +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); + GNUNET_free (deposit_confirmation->json_enc); + 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 @@ -0,0 +1,245 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + 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 auditor-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 "auditor_api_handle.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 connection to auditor this request handle will use + */ + struct TALER_AUDITOR_Handle *auditor; + + /** + * 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 /deposit-confirmation 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; + + leh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + ja = json_object_get (json, + "exchanges"); + if ( (NULL == ja) || + (! json_is_array (ja)) ) + { + GNUNET_break (0); + response_code = 0; + break; + } + + ja_len = json_array_size (ja); + if (ja_len > MAX_EXCHANGES) + { + GNUNET_break (0); + response_code = 0; + break; + } + { + struct TALER_AUDITOR_ExchangeInfo ei[ja_len]; + int ok; + + ok = GNUNET_YES; + 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 = GNUNET_NO; + break; + } + } + if (GNUNET_YES != ok) + break; + leh->cb (leh->cb_cls, + response_code, + TALER_EC_NONE, + ja_len, + ei, + json); + leh->cb = NULL; + } + break; + 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 */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != leh->cb) + leh->cb (leh->cb_cls, + response_code, + TALER_JSON_get_error_code (json), + 0, + NULL, + json); + TALER_AUDITOR_list_exchanges_cancel (leh); +} + + +/** + * Submit an /exchanges request to the auditor and get the + * auditor's response. If the auditor's reply is not + * well-formed, we return an HTTP status code of zero to @a cb. + * + * @param auditor the auditor handle; the auditor must be ready to operate + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + * signatures fail to verify). In this case, the callback is not called. + */ +struct TALER_AUDITOR_ListExchangesHandle * +TALER_AUDITOR_list_exchanges (struct TALER_AUDITOR_Handle *auditor, + TALER_AUDITOR_ListExchangesResultCallback cb, + void *cb_cls) +{ + struct TALER_AUDITOR_ListExchangesHandle *leh; + struct GNUNET_CURL_Context *ctx; + CURL *eh; + + GNUNET_assert (GNUNET_YES == + MAH_handle_is_ready (auditor)); + + leh = GNUNET_new (struct TALER_AUDITOR_ListExchangesHandle); + leh->auditor = auditor; + leh->cb = cb; + leh->cb_cls = cb_cls; + leh->url = MAH_path_to_url (auditor, "/exchanges"); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for list-exchanges: `%s'\n", + leh->url); + eh = TAL_curl_easy_get (leh->url); + ctx = MAH_handle_to_context (auditor); + leh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_NO, + &handle_exchanges_finished, + leh); + return leh; +} + + +/** + * Cancel a deposit-confirmation permission request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param deposit-confirmation the deposit-confirmation permission request handle + */ +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_handle.c b/src/lib/auditor_api_handle.c @@ -0,0 +1,527 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + 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 auditor-lib/auditor_api_handle.c + * @brief Implementation of the "handle" component of 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_handle.h" +#include "auditor_api_curl_defaults.h" +#include "backoff.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)); + +/** + * Stages of initialization for the `struct TALER_AUDITOR_Handle` + */ +enum AuditorHandleState +{ + /** + * Just allocated. + */ + MHS_INIT = 0, + + /** + * Obtained the auditor's versioning data and version. + */ + MHS_VERSION = 1, + + /** + * Failed to initialize (fatal). + */ + MHS_FAILED = 2 +}; + + +/** + * Data for the request to get the /version of a auditor. + */ +struct VersionRequest; + + +/** + * Handle to the auditor + */ +struct TALER_AUDITOR_Handle +{ + /** + * The context of this handle + */ + struct GNUNET_CURL_Context *ctx; + + /** + * The URL of the auditor (i.e. "http://auditor.taler.net/") + */ + char *url; + + /** + * Function to call with the auditor's certification data, + * NULL if this has already been done. + */ + TALER_AUDITOR_VersionCallback version_cb; + + /** + * Closure to pass to @e version_cb. + */ + void *version_cb_cls; + + /** + * Data for the request to get the /version of a auditor, + * NULL once we are past stage #MHS_INIT. + */ + struct VersionRequest *vr; + + /** + * Task for retrying /version request. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * /version data of the auditor, only valid if + * @e handshake_complete is past stage #MHS_VERSION. + */ + struct TALER_AUDITOR_VersionInformation vi; + + /** + * Retry /version frequency. + */ + struct GNUNET_TIME_Relative retry_delay; + + /** + * Stage of the auditor's initialization routines. + */ + enum AuditorHandleState state; + +}; + + +/* ***************** Internal /version fetching ************* */ + +/** + * Data for the request to get the /version of a auditor. + */ +struct VersionRequest +{ + /** + * The connection to auditor this request handle will use + */ + struct TALER_AUDITOR_Handle *auditor; + + /** + * The url for this handle + */ + char *url; + + /** + * Entry for this request with the `struct GNUNET_CURL_Context`. + */ + struct GNUNET_CURL_Job *job; + +}; + + +/** + * Release memory occupied by a version request. + * Note that this does not cancel the request + * itself. + * + * @param vr request to free + */ +static void +free_version_request (struct VersionRequest *vr) +{ + GNUNET_free (vr->url); + GNUNET_free (vr); +} + + +/** + * Free version data object. + * + * @param vi data to free (pointer itself excluded) + */ +static void +free_version_info (struct TALER_AUDITOR_VersionInformation *vi) +{ + GNUNET_free_non_null (vi->version); + vi->version = NULL; +} + + +/** + * Decode the JSON in @a resp_obj from the /version response and store the data + * in the @a key_data. + * + * @param[in] resp_obj JSON object to parse + * @param check_sig #GNUNET_YES if we should check the signature + * @param[out] vi where to store the results we decoded + * @param[out] vc where to store version compatibility data + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error (malformed JSON) + */ +static int +decode_version_json (const json_t *resp_obj, + int check_sig, + struct TALER_AUDITOR_VersionInformation *vi, + enum TALER_AUDITOR_VersionCompatibility *vc) +{ + unsigned int age; + unsigned int revision; + unsigned int current; + const char *ver; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("version", + &ver), + GNUNET_JSON_spec_fixed_auto ("master_public_key", + &vi->auditor_pub), + GNUNET_JSON_spec_end() + }; + + if (JSON_OBJECT != json_typeof (resp_obj)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* check the version */ + if (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (3 != sscanf (vi->version, + "%u:%u:%u", + &current, + &revision, + &age)) + { + GNUNET_break_op (0); + free_version_info (vi); + return GNUNET_SYSERR; + } + vi->version = GNUNET_strdup (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 GNUNET_OK; +} + + +/** + * Initiate download of /version from the auditor. + * + * @param cls auditor where to download /version from + */ +static void +request_version (void *cls); + + +/** + * Callback used when downloading the reply to a /version request + * is complete. + * + * @param cls the `struct VersionRequest` + * @param response_code HTTP response code, 0 on error + * @param resp_obj parsed JSON result, NULL on error + */ +static void +version_completed_cb (void *cls, + long response_code, + const void *gresp_obj) +{ + const json_t *resp_obj = gresp_obj; + struct VersionRequest *vr = cls; + struct TALER_AUDITOR_Handle *auditor = vr->auditor; + enum TALER_AUDITOR_VersionCompatibility vc; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received version from URL `%s' with status %ld.\n", + vr->url, + response_code); + vc = TALER_AUDITOR_VC_PROTOCOL_ERROR; + switch (response_code) + { + case 0: + case MHD_HTTP_INTERNAL_SERVER_ERROR: + free_version_request (vr); + auditor->vr = NULL; + GNUNET_assert (NULL == auditor->retry_task); + auditor->retry_delay = EXCHANGE_LIB_BACKOFF (auditor->retry_delay); + auditor->retry_task = GNUNET_SCHEDULER_add_delayed (auditor->retry_delay, + &request_version, + auditor); + return; + case MHD_HTTP_OK: + if (NULL == resp_obj) + { + response_code = 0; + break; + } + if (GNUNET_OK != + decode_version_json (resp_obj, + GNUNET_YES, + &auditor->vi, + &vc)) + { + response_code = 0; + break; + } + auditor->retry_delay = GNUNET_TIME_UNIT_ZERO; + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + break; + } + if (MHD_HTTP_OK != response_code) + { + auditor->vr = NULL; + free_version_request (vr); + auditor->state = MHS_FAILED; + free_version_info (&auditor->vi); + /* notify application that we failed */ + auditor->version_cb (auditor->version_cb_cls, + NULL, + vc); + return; + } + + auditor->vr = NULL; + free_version_request (vr); + auditor->state = MHS_VERSION; + /* notify application about the key information */ + auditor->version_cb (auditor->version_cb_cls, + &auditor->vi, + vc); +} + + +/* ********************* library internal API ********* */ + + +/** + * Get the context of a auditor. + * + * @param h the auditor handle to query + * @return ctx context to execute jobs in + */ +struct GNUNET_CURL_Context * +MAH_handle_to_context (struct TALER_AUDITOR_Handle *h) +{ + return h->ctx; +} + + +/** + * Check if the handle is ready to process requests. + * + * @param h the auditor handle to query + * @return #GNUNET_YES if we are ready, #GNUNET_NO if not + */ +int +MAH_handle_is_ready (struct TALER_AUDITOR_Handle *h) +{ + return (MHS_VERSION == h->state) ? GNUNET_YES : GNUNET_NO; +} + + +/** + * Obtain the URL to use for an API request. + * + * @param h handle for the auditor + * @param path Taler API path (i.e. "/deposit-confirmation") + * @return the full URL to use with cURL + */ +char * +MAH_path_to_url (struct TALER_AUDITOR_Handle *h, + const char *path) +{ + return MAH_path_to_url2 (h->url, + path); +} + + +/** + * Obtain the URL to use for an API request. + * + * @param base_url base URL of the auditor (i.e. "http://auditor/") + * @param path Taler API path (i.e. "/deposit-confirmation") + * @return the full URL to use with cURL + */ +char * +MAH_path_to_url2 (const char *base_url, + const char *path) +{ + char *url; + + if ( ('/' == path[0]) && + (0 < strlen (base_url)) && + ('/' == base_url[strlen (base_url) - 1]) ) + path++; /* avoid generating URL with "//" from concat */ + GNUNET_asprintf (&url, + "%s%s", + base_url, + path); + return url; +} + + +/* ********************* public API ******************* */ + + +/** + * Initialise a connection to the auditor. Will connect to the + * auditor and obtain information about the auditor's master public + * key and the auditor's auditor. The respective information will + * be passed to the @a version_cb once available, and all future + * interactions with the auditor will be checked to be signed + * (where appropriate) by the respective master key. + * + * @param ctx the context + * @param url HTTP base URL for the auditor + * @param version_cb function to call with the auditor's version information + * @param version_cb_cls closure for @a version_cb + * @return the auditor handle; NULL upon error + */ +struct TALER_AUDITOR_Handle * +TALER_AUDITOR_connect (struct GNUNET_CURL_Context *ctx, + const char *url, + TALER_AUDITOR_VersionCallback version_cb, + void *version_cb_cls) +{ + struct TALER_AUDITOR_Handle *auditor; + + auditor = GNUNET_new (struct TALER_AUDITOR_Handle); + auditor->ctx = ctx; + auditor->url = GNUNET_strdup (url); + auditor->version_cb = version_cb; + auditor->version_cb_cls = version_cb_cls; + auditor->retry_task = GNUNET_SCHEDULER_add_now (&request_version, + auditor); + return auditor; +} + + +/** + * Initiate download of /version from the auditor. + * + * @param cls auditor where to download /version from + */ +static void +request_version (void *cls) +{ + struct TALER_AUDITOR_Handle *auditor = cls; + struct VersionRequest *vr; + CURL *eh; + + auditor->retry_task = NULL; + GNUNET_assert (NULL == auditor->vr); + vr = GNUNET_new (struct VersionRequest); + vr->auditor = auditor; + vr->url = MAH_path_to_url (auditor, + "/version"); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting version with URL `%s'.\n", + vr->url); + eh = TAL_curl_easy_get (vr->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_VERBOSE, + 0)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT, + (long) 300)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + vr)); + vr->job = GNUNET_CURL_job_add (auditor->ctx, + eh, + GNUNET_NO, + &version_completed_cb, + vr); + auditor->vr = vr; +} + + +/** + * Disconnect from the auditor + * + * @param auditor the auditor handle + */ +void +TALER_AUDITOR_disconnect (struct TALER_AUDITOR_Handle *auditor) +{ + if (NULL != auditor->vr) + { + GNUNET_CURL_job_cancel (auditor->vr->job); + free_version_request (auditor->vr); + auditor->vr = NULL; + } + free_version_info (&auditor->vi); + if (NULL != auditor->retry_task) + { + GNUNET_SCHEDULER_cancel (auditor->retry_task); + auditor->retry_task = NULL; + } + GNUNET_free (auditor->url); + GNUNET_free (auditor); +} + + +/* end of auditor_api_handle.c */ diff --git a/src/auditor-lib/auditor_api_handle.h b/src/lib/auditor_api_handle.h diff --git a/src/exchange-lib/backoff.h b/src/lib/backoff.h diff --git a/src/exchange-lib/baseline/admin_add_incoming.req b/src/lib/baseline/admin_add_incoming.req diff --git a/src/exchange-lib/baseline/deposit.req b/src/lib/baseline/deposit.req diff --git a/src/exchange-lib/baseline/keys.req b/src/lib/baseline/keys.req diff --git a/src/exchange-lib/baseline/refresh_link.req b/src/lib/baseline/refresh_link.req diff --git a/src/exchange-lib/baseline/refresh_melt.req b/src/lib/baseline/refresh_melt.req diff --git a/src/exchange-lib/baseline/refresh_reveal.req b/src/lib/baseline/refresh_reveal.req diff --git a/src/exchange-lib/baseline/reserve_status.req b/src/lib/baseline/reserve_status.req diff --git a/src/exchange-lib/baseline/reserve_withdraw.req b/src/lib/baseline/reserve_withdraw.req diff --git a/src/exchange-lib/baseline/wire.req b/src/lib/baseline/wire.req diff --git a/src/exchange-lib/baseline/wire_sepa.req b/src/lib/baseline/wire_sepa.req diff --git a/src/exchange-lib/baseline/wire_test.req b/src/lib/baseline/wire_test.req diff --git a/src/exchange-lib/exchange_api_common.c b/src/lib/exchange_api_common.c diff --git a/src/lib/exchange_api_curl_defaults.c b/src/lib/exchange_api_curl_defaults.c @@ -0,0 +1,75 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + 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_curl_defaults.c + * @brief curl easy handle defaults + * @author Florian Dold + */ + +#include "exchange_api_curl_defaults.h" + + +/** + * Get a curl handle with the right defaults + * for the exchange lib. In the future, we might manage a pool of connections here. + * + * @param url URL to query + */ +CURL * +TEL_curl_easy_get (const char *url) +{ + CURL *eh; + + eh = curl_easy_init (); + + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_ENCODING, + "deflate")); +#ifdef CURLOPT_TCP_FASTOPEN + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TCP_FASTOPEN, + 1L)); +#endif + { + /* Unfortunately libcurl needs chunk to be alive until after + curl_easy_perform. To avoid manual cleanup, we keep + one static list here. */ + static struct curl_slist *chunk = NULL; + if (NULL == chunk) + { + /* With POST requests, we do not want to wait for the + "100 Continue" response, as our request bodies are usually + small and directy sending them saves us a round trip. + + Clearing the expect header like this disables libcurl's + default processing of the header. + + Disabling this header is safe for other HTTP methods, thus + we don't distinguish further before setting it. */ + chunk = curl_slist_append (chunk, "Expect:"); + } + GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HTTPHEADER, chunk)); + } + + return eh; +} diff --git a/src/lib/exchange_api_curl_defaults.h b/src/lib/exchange_api_curl_defaults.h @@ -0,0 +1,41 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + 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_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 exchange lib. In the future, we might manage a pool of connections here. + * + * @param url URL to query + */ +CURL * +TEL_curl_easy_get (const char *url); + +#endif /* _TALER_CURL_DEFAULTS_H */ diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c @@ -0,0 +1,597 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2018 GNUnet e.V. + + 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 exchange-lib/exchange_api_deposit.c + * @brief Implementation of the /deposit request of the exchange's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @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_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Deposit Handle + */ +struct TALER_EXCHANGE_DepositHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_DepositResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Information the exchange should sign in response. + */ + struct TALER_DepositConfirmationPS depconf; + + /** + * Value of the /deposit transaction, including fee. + */ + struct TALER_Amount amount_with_fee; + + /** + * Total value of the coin being transacted with. + */ + struct TALER_Amount coin_value; + +}; + + +/** + * Signature of functions called with the result from our call to the + * auditor's /deposit-confirmation handler. + * + * @param cls closure + * @param http_status HTTP status code, 200 on success + * @param ec taler protocol error status code, 0 on success + * @param json raw json response + */ +static void +acc_confirmation_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const json_t *json) +{ + /* FIXME: clean up state, some logging on errors! */ +} + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param dh deposit handle + * @param json json reply with the signature + * @param exchange_sig[out] set to the exchange's signature + * @param exchange_pub[out] set to the exchange's public key + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_deposit_signature_ok (const struct TALER_EXCHANGE_DepositHandle *dh, + const json_t *json, + struct TALER_ExchangeSignatureP *exchange_sig, + struct TALER_ExchangePublicKeyP *exchange_pub) +{ + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("sig", exchange_sig), + GNUNET_JSON_spec_fixed_auto ("pub", exchange_pub), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = TALER_EXCHANGE_get_keys (dh->exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT, + &dh->depconf.purpose, + &exchange_sig->eddsa_signature, + &exchange_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 /* #5447: replace with "for all auditors, if auditor selected for DC notification... */) + { + struct TALER_AUDITOR_DepositConfirmationHandle *dch; + const struct TALER_EXCHANGE_SigningPublicKey *spk; + struct TALER_Amount amount_without_fee; + + spk = TALER_EXCHANGE_get_signing_key_details (key_state, + exchange_pub); + GNUNET_assert (NULL != spk); + TALER_amount_ntoh (&amount_without_fee, + &dh->depconf.amount_without_fee); + dch = TALER_AUDITOR_deposit_confirmation (NULL /* FIXME: auditor */, + &dh->depconf.h_wire, + &dh->depconf.h_contract_terms, + GNUNET_TIME_absolute_ntoh (dh->depconf.timestamp), + GNUNET_TIME_absolute_ntoh (dh->depconf.refund_deadline), + &amount_without_fee, + &dh->depconf.coin_pub, + &dh->depconf.merchant, + exchange_pub, + exchange_sig, + &key_state->master_pub, + spk->valid_from, + spk->valid_until, + spk->valid_legal, + &spk->master_sig, + &acc_confirmation_cb, + NULL /* FIXME: context! */); + } + + + return GNUNET_OK; +} + + +/** + * Verify that the signatures on the "403 FORBIDDEN" response from the + * exchange demonstrating customer double-spending are valid. + * + * @param dh deposit handle + * @param json json reply with the signature(s) and transaction history + * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not + */ +static int +verify_deposit_signature_forbidden (const struct TALER_EXCHANGE_DepositHandle *dh, + const json_t *json) +{ + json_t *history; + struct TALER_Amount total; + + history = json_object_get (json, + "history"); + if (GNUNET_OK != + TALER_EXCHANGE_verify_coin_history (dh->coin_value.currency, + &dh->depconf.coin_pub, + history, + &total)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&total, + &total, + &dh->amount_with_fee)) + { + /* clearly not OK if our transaction would have caused + the overflow... */ + return GNUNET_OK; + } + + if (0 >= TALER_amount_cmp (&total, + &dh->coin_value)) + { + /* transaction should have still fit */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* everything OK, proof of double-spending was provided */ + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /deposit request. + * + * @param cls the `struct TALER_EXCHANGE_DepositHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_deposit_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_DepositHandle *dh = cls; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP *es = NULL; + struct TALER_ExchangePublicKeyP *ep = NULL; + const json_t *j = response; + + dh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + verify_deposit_signature_ok (dh, + j, + &exchange_sig, + &exchange_pub)) + { + GNUNET_break_op (0); + response_code = 0; + } + else + { + es = &exchange_sig; + ep = &exchange_pub; + } + 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_FORBIDDEN: + /* Double spending; check signatures on transaction history */ + if (GNUNET_OK != + verify_deposit_signature_forbidden (dh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + dh->cb (dh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + es, + ep, + j); + TALER_EXCHANGE_deposit_cancel (dh); +} + + +/** + * Verify signature information about the deposit. + * + * @param dki public key information + * @param amount the amount to be deposited + * @param h_wire hash of the merchant’s account details + * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) + * @param coin_pub coin’s public key + * @param denom_pub denomination key with which the coin is signed + * @param denom_sig exchange’s unblinded signature of the coin + * @param timestamp timestamp when the deposit was finalized + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed) + * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. + * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not + */ +static int +verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki, + const struct TALER_Amount *amount, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_DenominationSignature *denom_sig, + const struct TALER_DenominationPublicKey *denom_pub, + struct GNUNET_TIME_Absolute timestamp, + const struct TALER_MerchantPublicKeyP *merchant_pub, + struct GNUNET_TIME_Absolute refund_deadline, + const struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_DepositRequestPS dr; + struct TALER_CoinPublicInfo coin_info; + + dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); + dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS)); + dr.h_contract_terms = *h_contract_terms; + dr.h_wire = *h_wire; + dr.timestamp = GNUNET_TIME_absolute_hton (timestamp); + dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); + TALER_amount_hton (&dr.amount_with_fee, + amount); + TALER_amount_hton (&dr.deposit_fee, + &dki->fee_deposit); + dr.merchant = *merchant_pub; + dr.coin_pub = *coin_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT, + &dr.purpose, + &coin_sig->eddsa_signature, + &coin_pub->eddsa_pub)) + { + GNUNET_break_op (0); + TALER_LOG_WARNING ("Invalid coin signature on /deposit request!\n"); + { + TALER_LOG_DEBUG ("... amount_with_fee was %s\n", + TALER_amount2s (amount)); + TALER_LOG_DEBUG ("... deposit_fee was %s\n", + TALER_amount2s (&dki->fee_deposit)); + } + + return GNUNET_SYSERR; + } + + /* check coin signature */ + coin_info.coin_pub = *coin_pub; + coin_info.denom_pub = *denom_pub; + coin_info.denom_sig = *denom_sig; + if (GNUNET_YES != + TALER_test_coin_valid (&coin_info)) + { + GNUNET_break_op (0); + TALER_LOG_WARNING ("Invalid coin passed for /deposit\n"); + return GNUNET_SYSERR; + } + if (0 < TALER_amount_cmp (&dki->fee_deposit, + amount)) + { + GNUNET_break_op (0); + TALER_LOG_WARNING ("Deposit amount smaller than fee\n"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Submit a deposit permission to the exchange and get the exchange's response. + * Note that while we return the response verbatim to the caller for + * further processing, we do already verify that the response is + * well-formed (i.e. that signatures included in the response are all + * valid). If the exchange's reply is not well-formed, we return an + * HTTP status code of zero to @a cb. + * + * We also verify that the @a coin_sig is valid for this deposit + * request, and that the @a ub_sig is a valid signature for @a + * coin_pub. Also, the @a exchange must be ready to operate (i.e. have + * finished processing the /keys reply). If either check fails, we do + * NOT initiate the transaction with the exchange and instead return NULL. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param amount the amount to be deposited + * @param wire_deadline date until which the merchant would like the exchange to settle the balance (advisory, the exchange cannot be + * forced to settle in the past or upon very short notice, but of course a well-behaved exchange will limit aggregation based on the advice received) + * @param wire_details the merchant’s account details, in a format supported by the exchange + * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) + * @param coin_pub coin’s public key + * @param denom_pub denomination key with which the coin is signed + * @param denom_sig exchange’s unblinded signature of the coin + * @param timestamp timestamp when the contract was finalized, must not be too far in the future + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline + * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + * signatures fail to verify). In this case, the callback is not called. + */ +struct TALER_EXCHANGE_DepositHandle * +TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Absolute wire_deadline, + json_t *wire_details, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_DenominationSignature *denom_sig, + const struct TALER_DenominationPublicKey *denom_pub, + struct GNUNET_TIME_Absolute timestamp, + const struct TALER_MerchantPublicKeyP *merchant_pub, + struct GNUNET_TIME_Absolute refund_deadline, + const struct TALER_CoinSpendSignatureP *coin_sig, + TALER_EXCHANGE_DepositResultCallback cb, + void *cb_cls) +{ + const struct TALER_EXCHANGE_Keys *key_state; + const struct TALER_EXCHANGE_DenomPublicKey *dki; + struct TALER_EXCHANGE_DepositHandle *dh; + struct GNUNET_CURL_Context *ctx; + json_t *deposit_obj; + CURL *eh; + struct GNUNET_HashCode h_wire; + struct TALER_Amount amount_without_fee; + + (void) GNUNET_TIME_round_abs (&wire_deadline); + (void) GNUNET_TIME_round_abs (&refund_deadline); + GNUNET_assert (refund_deadline.abs_value_us <= wire_deadline.abs_value_us); + GNUNET_assert (GNUNET_YES == + TEAH_handle_is_ready (exchange)); + /* initialize h_wire */ + if (GNUNET_OK != + TALER_JSON_merchant_wire_signature_hash (wire_details, + &h_wire)) + { + GNUNET_break (0); + return NULL; + } + key_state = TALER_EXCHANGE_get_keys (exchange); + dki = TALER_EXCHANGE_get_denomination_key (key_state, + denom_pub); + GNUNET_assert (NULL != dki); + GNUNET_assert (GNUNET_SYSERR != + TALER_amount_subtract (&amount_without_fee, + amount, + &dki->fee_deposit)); + if (GNUNET_OK != + verify_signatures (dki, + amount, + &h_wire, + h_contract_terms, + coin_pub, + denom_sig, + denom_pub, + timestamp, + merchant_pub, + refund_deadline, + coin_sig)) + { + GNUNET_break_op (0); + return NULL; + } + + deposit_obj = json_pack ("{s:o, s:O," /* f/wire */ + " s:o, s:o," /* H_wire, h_contract_terms */ + " s:o, s:o," /* coin_pub, denom_pub */ + " s:o, s:o," /* ub_sig, timestamp */ + " s:o," /* merchant_pub */ + " s:o, s:o," /* refund_deadline, wire_deadline */ + " s:o}", /* coin_sig */ + "contribution", TALER_JSON_from_amount (amount), + "wire", wire_details, + "H_wire", GNUNET_JSON_from_data_auto (&h_wire), + "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms), + "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), + "denom_pub", GNUNET_JSON_from_rsa_public_key (denom_pub->rsa_public_key), + "ub_sig", GNUNET_JSON_from_rsa_signature (denom_sig->rsa_signature), + "timestamp", GNUNET_JSON_from_time_abs (timestamp), + "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub), + "refund_deadline", GNUNET_JSON_from_time_abs (refund_deadline), + "wire_transfer_deadline", GNUNET_JSON_from_time_abs (wire_deadline), + "coin_sig", GNUNET_JSON_from_data_auto (coin_sig) + ); + if (NULL == deposit_obj) + { + GNUNET_break (0); + return NULL; + } + + dh = GNUNET_new (struct TALER_EXCHANGE_DepositHandle); + dh->exchange = exchange; + dh->cb = cb; + dh->cb_cls = cb_cls; + dh->url = TEAH_path_to_url (exchange, "/deposit"); + dh->depconf.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)); + dh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT); + dh->depconf.h_contract_terms = *h_contract_terms; + dh->depconf.h_wire = h_wire; + dh->depconf.timestamp = GNUNET_TIME_absolute_hton (timestamp); + dh->depconf.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); + TALER_amount_hton (&dh->depconf.amount_without_fee, + &amount_without_fee); + dh->depconf.coin_pub = *coin_pub; + dh->depconf.merchant = *merchant_pub; + dh->amount_with_fee = *amount; + dh->coin_value = dki->value; + + eh = TEL_curl_easy_get (dh->url); + GNUNET_assert (NULL != (dh->json_enc = + json_dumps (deposit_obj, + JSON_COMPACT))); + json_decref (deposit_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for deposit: `%s'\n", + dh->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + dh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (dh->json_enc))); + ctx = TEAH_handle_to_context (exchange); + dh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_deposit_finished, + dh); + return dh; +} + + +/** + * Cancel a deposit permission request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param deposit the deposit permission request handle + */ +void +TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit) +{ + if (NULL != deposit->job) + { + GNUNET_CURL_job_cancel (deposit->job); + deposit->job = NULL; + } + GNUNET_free (deposit->url); + GNUNET_free (deposit->json_enc); + GNUNET_free (deposit); +} + + +/* end of exchange_api_deposit.c */ diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c @@ -0,0 +1,1779 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + 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 exchange-lib/exchange_api_handle.c + * @brief Implementation of the "handle" component of the exchange'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_exchange_service.h" +#include "taler_auditor_service.h" +#include "taler_signatures.h" +#include "exchange_api_handle.h" +#include "exchange_api_curl_defaults.h" +#include "backoff.h" + +/** + * Which revision of the Taler protocol is implemented + * by this library? Used to determine compatibility. + */ +#define TALER_PROTOCOL_CURRENT 2 + +/** + * How many revisions back are we compatible to? + */ +#define TALER_PROTOCOL_AGE 0 + +/** + * Current version for (local) JSON serialization of persisted + * /keys data. + */ +#define TALER_SERIALIZATION_FORMAT_VERSION 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)); + +/** + * Stages of initialization for the `struct TALER_EXCHANGE_Handle` + */ +enum ExchangeHandleState +{ + /** + * Just allocated. + */ + MHS_INIT = 0, + + /** + * Obtained the exchange's certification data and keys. + */ + MHS_CERT = 1, + + /** + * Failed to initialize (fatal). + */ + MHS_FAILED = 2 +}; + + +/** + * Data for the request to get the /keys of a exchange. + */ +struct KeysRequest; + + +/** + * Entry in list of ongoing interactions with an auditor. + */ +struct AuditorInteractionEntry +{ + /** + * DLL entry. + */ + struct AuditorInteractionEntry *next; + + /** + * DLL entry. + */ + struct AuditorInteractionEntry *prev; + + /** + * Interaction state. + */ + struct TALER_AUDITOR_DepositConfirmationHandle *dch; +}; + + +/** + * Entry in DLL of auditors used by an exchange. + */ +struct AuditorListEntry +{ + /** + * Next pointer of DLL. + */ + struct AuditorListEntry *next; + + /** + * Prev pointer of DLL. + */ + struct AuditorListEntry *prev; + + /** + * Base URL of the auditor. + */ + const char *auditor_url; + + /** + * Handle to the auditor. + */ + struct TALER_AUDITOR_Handle *ah; + + /** + * Head of DLL of interactions with this auditor. + */ + struct AuditorInteractionEntry *ai_head; + + /** + * Tail of DLL of interactions with this auditor. + */ + struct AuditorInteractionEntry *ai_tail; + + /** + * Public key of the auditor. + */ + struct TALER_AuditorPublicKeyP auditor_pub; + + /** + * Flag indicating that the auditor is available and that protocol + * version compatibility is given. + */ + int is_up; + +}; + + +/** + * Handle to the exchange + */ +struct TALER_EXCHANGE_Handle +{ + /** + * The context of this handle + */ + struct GNUNET_CURL_Context *ctx; + + /** + * The URL of the exchange (i.e. "http://exchange.taler.net/") + */ + char *url; + + /** + * Function to call with the exchange's certification data, + * NULL if this has already been done. + */ + TALER_EXCHANGE_CertificationCallback cert_cb; + + /** + * Closure to pass to @e cert_cb. + */ + void *cert_cb_cls; + + /** + * Data for the request to get the /keys of a exchange, + * NULL once we are past stage #MHS_INIT. + */ + struct KeysRequest *kr; + + /** + * Task for retrying /keys request. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * Raw key data of the exchange, only valid if + * @e handshake_complete is past stage #MHS_CERT. + */ + json_t *key_data_raw; + + /** + * Head of DLL of auditors of this exchange. + */ + struct AuditorListEntry *auditors_head; + + /** + * Tail of DLL of auditors of this exchange. + */ + struct AuditorListEntry *auditors_tail; + + /** + * Key data of the exchange, only valid if + * @e handshake_complete is past stage #MHS_CERT. + */ + struct TALER_EXCHANGE_Keys key_data; + + /** + * Retry /keys frequency. + */ + struct GNUNET_TIME_Relative retry_delay; + + /** + * When does @e key_data expire? + */ + struct GNUNET_TIME_Absolute key_data_expiration; + + /** + * Stage of the exchange's initialization routines. + */ + enum ExchangeHandleState state; + +}; + + +/* ***************** Internal /keys fetching ************* */ + +/** + * Data for the request to get the /keys of a exchange. + */ +struct KeysRequest +{ + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this handle + */ + char *url; + + /** + * Entry for this request with the `struct GNUNET_CURL_Context`. + */ + struct GNUNET_CURL_Job *job; + + /** + * Expiration time according to "Expire:" header. + * 0 if not provided by the server. + */ + struct GNUNET_TIME_Absolute expire; + +}; + + +/** + * Iterate over all available auditors for @a h, calling + * @param ah and giving it a chance to start a deposit + * confirmation interaction. + * + * @param h exchange to go over auditors for + * @param ac function to call per auditor + * @param ac_cls closure for @a ac + */ +void +TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h, + TEAH_AuditorCallback ac, + void *ac_cls) +{ + // FIXME! +} + + +/** + * Release memory occupied by a keys request. + * Note that this does not cancel the request + * itself. + * + * @param kr request to free + */ +static void +free_keys_request (struct KeysRequest *kr) +{ + GNUNET_free (kr->url); + GNUNET_free (kr); +} + + +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +/** + * Parse a exchange's signing key encoded in JSON. + * + * @param[out] sign_key where to return the result + * @param check_sigs should we check signatures? + * @param[in] sign_key_obj json to parse + * @param master_key master key to use to verify signature + * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is + * invalid or the json malformed. + */ +static int +parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, + int check_sigs, + json_t *sign_key_obj, + const struct TALER_MasterPublicKeyP *master_key) +{ + struct TALER_ExchangeSigningKeyValidityPS sign_key_issue; + struct TALER_MasterSignatureP sign_key_issue_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("master_sig", + &sign_key_issue_sig), + GNUNET_JSON_spec_fixed_auto ("key", + &sign_key->key), + GNUNET_JSON_spec_absolute_time ("stamp_start", + &sign_key->valid_from), + GNUNET_JSON_spec_absolute_time ("stamp_expire", + &sign_key->valid_until), + GNUNET_JSON_spec_absolute_time ("stamp_end", + &sign_key->valid_legal), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (sign_key_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (! check_sigs) + return GNUNET_OK; + sign_key_issue.signkey_pub = sign_key->key; + sign_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY); + sign_key_issue.purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS)); + sign_key_issue.master_public_key = *master_key; + sign_key_issue.start = GNUNET_TIME_absolute_hton (sign_key->valid_from); + sign_key_issue.expire = GNUNET_TIME_absolute_hton (sign_key->valid_until); + sign_key_issue.end = GNUNET_TIME_absolute_hton (sign_key->valid_legal); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY, + &sign_key_issue.purpose, + &sign_key_issue_sig.eddsa_signature, + &master_key->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + sign_key->master_sig = sign_key_issue_sig; + return GNUNET_OK; +} + + +/** + * Parse a exchange's denomination key encoded in JSON. + * + * @param[out] denom_key where to return the result + * @param check_sigs should we check signatures? + * @param[in] denom_key_obj json to parse + * @param master_key master key to use to verify signature + * @param hash_context where to accumulate data for signature verification + * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is + * invalid or the json malformed. + */ +static int +parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key, + int check_sigs, + json_t *denom_key_obj, + struct TALER_MasterPublicKeyP *master_key, + struct GNUNET_HashContext *hash_context) +{ + struct TALER_DenominationKeyValidityPS denom_key_issue; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("master_sig", + &denom_key->master_sig), + GNUNET_JSON_spec_absolute_time ("stamp_expire_deposit", + &denom_key->expire_deposit), + GNUNET_JSON_spec_absolute_time ("stamp_expire_withdraw", + &denom_key->withdraw_valid_until), + GNUNET_JSON_spec_absolute_time ("stamp_start", + &denom_key->valid_from), + GNUNET_JSON_spec_absolute_time ("stamp_expire_legal", + &denom_key->expire_legal), + TALER_JSON_spec_amount ("value", + &denom_key->value), + TALER_JSON_spec_amount ("fee_withdraw", + &denom_key->fee_withdraw), + TALER_JSON_spec_amount ("fee_deposit", + &denom_key->fee_deposit), + TALER_JSON_spec_amount ("fee_refresh", + &denom_key->fee_refresh), + TALER_JSON_spec_amount ("fee_refund", + &denom_key->fee_refund), + GNUNET_JSON_spec_rsa_public_key ("denom_pub", + &denom_key->key.rsa_public_key), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (denom_key_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + GNUNET_CRYPTO_rsa_public_key_hash (denom_key->key.rsa_public_key, + &denom_key->h_key); + if (! check_sigs) + return GNUNET_OK; + memset (&denom_key_issue, + 0, + sizeof (denom_key_issue)); + denom_key_issue.purpose.purpose + = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY); + denom_key_issue.purpose.size + = htonl (sizeof (struct TALER_DenominationKeyValidityPS)); + denom_key_issue.master = *master_key; + denom_key_issue.denom_hash = denom_key->h_key; + denom_key_issue.start = GNUNET_TIME_absolute_hton (denom_key->valid_from); + denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (denom_key->withdraw_valid_until); + denom_key_issue.expire_deposit = GNUNET_TIME_absolute_hton (denom_key->expire_deposit); + denom_key_issue.expire_legal = GNUNET_TIME_absolute_hton (denom_key->expire_legal); + TALER_amount_hton (&denom_key_issue.value, + &denom_key->value); + TALER_amount_hton (&denom_key_issue.fee_withdraw, + &denom_key->fee_withdraw); + TALER_amount_hton (&denom_key_issue.fee_deposit, + &denom_key->fee_deposit); + TALER_amount_hton (&denom_key_issue.fee_refresh, + &denom_key->fee_refresh); + TALER_amount_hton (&denom_key_issue.fee_refund, + &denom_key->fee_refund); + EXITIF (GNUNET_SYSERR == + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY, + &denom_key_issue.purpose, + &denom_key->master_sig.eddsa_signature, + &master_key->eddsa_pub)); + GNUNET_CRYPTO_hash_context_read (hash_context, + &denom_key_issue.denom_hash, + sizeof (struct GNUNET_HashCode)); + return GNUNET_OK; + + EXITIF_exit: + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; +} + + +/** + * Parse a exchange's auditor information encoded in JSON. + * + * @param[out] auditor where to return the result + * @param check_sig should we check signatures + * @param[in] auditor_obj json to parse + * @param key_data information about denomination keys + * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is + * invalid or the json malformed. + */ +static int +parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, + int check_sigs, + json_t *auditor_obj, + const struct TALER_EXCHANGE_Keys *key_data) +{ + json_t *keys; + json_t *key; + unsigned int len; + unsigned int off; + unsigned int i; + const char *auditor_url; + struct TALER_ExchangeKeyValidityPS kv; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("auditor_pub", + &auditor->auditor_pub), + GNUNET_JSON_spec_string ("auditor_url", + &auditor_url), + GNUNET_JSON_spec_json ("denomination_keys", + &keys), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (auditor_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + auditor->auditor_url = GNUNET_strdup (auditor_url); + kv.purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS); + kv.purpose.size = htonl (sizeof (struct TALER_ExchangeKeyValidityPS)); + GNUNET_CRYPTO_hash (auditor_url, + strlen (auditor_url) + 1, + &kv.auditor_url_hash); + kv.master = key_data->master_pub; + len = json_array_size (keys); + auditor->denom_keys = GNUNET_new_array (len, + struct TALER_EXCHANGE_AuditorDenominationInfo); + i = 0; + off = 0; + json_array_foreach (keys, i, key) { + struct TALER_AuditorSignatureP auditor_sig; + struct GNUNET_HashCode denom_h; + const struct TALER_EXCHANGE_DenomPublicKey *dk; + unsigned int dk_off; + struct GNUNET_JSON_Specification kspec[] = { + GNUNET_JSON_spec_fixed_auto ("auditor_sig", + &auditor_sig), + GNUNET_JSON_spec_fixed_auto ("denom_pub_h", + &denom_h), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (key, + kspec, + NULL, NULL)) + { + GNUNET_break_op (0); + continue; + } + dk = NULL; + dk_off = UINT_MAX; + for (unsigned int j=0;j<key_data->num_denom_keys;j++) + { + if (0 == memcmp (&denom_h, + &key_data->denom_keys[j].h_key, + sizeof (struct GNUNET_HashCode))) + { + dk = &key_data->denom_keys[j]; + dk_off = j; + break; + } + } + if (NULL == dk) + { + GNUNET_break_op (0); + continue; + } + if (check_sigs) + { + kv.start = GNUNET_TIME_absolute_hton (dk->valid_from); + kv.expire_withdraw = GNUNET_TIME_absolute_hton (dk->withdraw_valid_until); + kv.expire_deposit = GNUNET_TIME_absolute_hton (dk->expire_deposit); + kv.expire_legal = GNUNET_TIME_absolute_hton (dk->expire_legal); + TALER_amount_hton (&kv.value, + &dk->value); + TALER_amount_hton (&kv.fee_withdraw, + &dk->fee_withdraw); + TALER_amount_hton (&kv.fee_deposit, + &dk->fee_deposit); + TALER_amount_hton (&kv.fee_refresh, + &dk->fee_refresh); + TALER_amount_hton (&kv.fee_refund, + &dk->fee_refund); + kv.denom_hash = dk->h_key; + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS, + &kv.purpose, + &auditor_sig.eddsa_sig, + &auditor->auditor_pub.eddsa_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + } + auditor->denom_keys[off].denom_key_offset = dk_off; + auditor->denom_keys[off].auditor_sig = auditor_sig; + off++; + } + auditor->num_denom_keys = off; + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Decode the JSON in @a resp_obj from the /keys response + * and store the data in the @a key_data. + * + * @param[in] resp_obj JSON object to parse + * @param check_sig #GNUNET_YES if we should check the signature + * @param[out] key_data where to store the results we decoded + * @param[out] where to store version compatibility data + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + * (malformed JSON) + */ +static int +decode_keys_json (const json_t *resp_obj, + int check_sig, + struct TALER_EXCHANGE_Keys *key_data, + enum TALER_EXCHANGE_VersionCompatibility *vc) +{ + struct TALER_ExchangeSignatureP sig; + struct GNUNET_HashContext *hash_context; + struct TALER_ExchangePublicKeyP pub; + unsigned int age; + unsigned int revision; + unsigned int current; + struct GNUNET_JSON_Specification mspec[] = { + GNUNET_JSON_spec_fixed_auto ("eddsa_sig", + &sig), + GNUNET_JSON_spec_fixed_auto ("eddsa_pub", + &pub), + /* sig and pub must be first, as we skip those if + check_sig is false! */ + GNUNET_JSON_spec_fixed_auto ("master_public_key", + &key_data->master_pub), + GNUNET_JSON_spec_absolute_time ("list_issue_date", + &key_data->list_issue_date), + GNUNET_JSON_spec_relative_time + ("reserve_closing_delay", + &key_data->reserve_closing_delay), + GNUNET_JSON_spec_end() + }; + + if (JSON_OBJECT != json_typeof (resp_obj)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* check the version */ + { + const char *ver; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("version", + &ver), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (3 != sscanf (ver, + "%u:%u:%u", + &current, + &revision, + &age)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *vc = TALER_EXCHANGE_VC_MATCH; + if (TALER_PROTOCOL_CURRENT < current) + { + *vc |= TALER_EXCHANGE_VC_NEWER; + if (TALER_PROTOCOL_CURRENT < current - age) + *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; + } + if (TALER_PROTOCOL_CURRENT > current) + { + *vc |= TALER_EXCHANGE_VC_OLDER; + if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current) + *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; + } + key_data->version = GNUNET_strdup (ver); + } + + hash_context = NULL; + EXITIF (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + (check_sig) ? mspec : &mspec[2], + NULL, NULL)); + + /* parse the master public key and issue date of the response */ + if (check_sig) + hash_context = GNUNET_CRYPTO_hash_context_start (); + + /* parse the signing keys */ + { + json_t *sign_keys_array; + json_t *sign_key_obj; + unsigned int index; + + EXITIF (NULL == (sign_keys_array = + json_object_get (resp_obj, + "signkeys"))); + EXITIF (JSON_ARRAY != json_typeof (sign_keys_array)); + EXITIF (0 == (key_data->num_sign_keys = + json_array_size (sign_keys_array))); + key_data->sign_keys + = GNUNET_new_array (key_data->num_sign_keys, + struct TALER_EXCHANGE_SigningPublicKey); + index = 0; + json_array_foreach (sign_keys_array, index, sign_key_obj) { + EXITIF (GNUNET_SYSERR == + parse_json_signkey (&key_data->sign_keys[index], + check_sig, + sign_key_obj, + &key_data->master_pub)); + } + } + + /* parse the denomination keys, merging with the + possibly EXISTING array as required (/keys cherry picking) */ + { + json_t *denom_keys_array; + json_t *denom_key_obj; + unsigned int index; + + EXITIF (NULL == (denom_keys_array = + json_object_get (resp_obj, + "denoms"))); + EXITIF (JSON_ARRAY != json_typeof (denom_keys_array)); + + index = 0; + json_array_foreach (denom_keys_array, index, denom_key_obj) { + struct TALER_EXCHANGE_DenomPublicKey dk; + bool found = false; + + EXITIF (GNUNET_SYSERR == + parse_json_denomkey (&dk, + check_sig, + denom_key_obj, + &key_data->master_pub, + hash_context)); + for (unsigned int j=0;j<key_data->num_denom_keys;j++) + { + if (0 == memcmp (&dk, + &key_data->denom_keys[j], + sizeof (dk))) + { + found = true; + break; + } + } + if (found) + { + /* 0:0:0 did not support /keys cherry picking */ + GNUNET_break_op (0 == current); + continue; + } + if (key_data->denom_keys_size == key_data->num_denom_keys) + GNUNET_array_grow (key_data->denom_keys, + key_data->denom_keys_size, + key_data->denom_keys_size * 2 + 2); + key_data->denom_keys[key_data->num_denom_keys++] = dk; + + /* Update "last_denom_issue_date" */ + TALER_LOG_DEBUG ("Crawling DK 'valid_from': %s\n", + GNUNET_STRINGS_absolute_time_to_string (dk.valid_from)); + key_data->last_denom_issue_date + = GNUNET_TIME_absolute_max (key_data->last_denom_issue_date, + dk.valid_from); + }; + } + /* parse the auditor information */ + { + json_t *auditors_array; + json_t *auditor_info; + unsigned int index; + + EXITIF (NULL == (auditors_array = + json_object_get (resp_obj, + "auditors"))); + EXITIF (JSON_ARRAY != json_typeof (auditors_array)); + + /* Merge with the existing auditor information we have (/keys cherry picking) */ + index = 0; + json_array_foreach (auditors_array, index, auditor_info) { + struct TALER_EXCHANGE_AuditorInformation ai; + bool found = false; + + memset (&ai, + 0, + sizeof (ai)); + EXITIF (GNUNET_SYSERR == + parse_json_auditor (&ai, + check_sig, + auditor_info, + key_data)); + for (unsigned int j=0;j<key_data->num_auditors;j++) + { + struct TALER_EXCHANGE_AuditorInformation *aix = &key_data->auditors[j]; + + if (0 == memcmp (&ai.auditor_pub, + &aix->auditor_pub, + sizeof (struct TALER_AuditorPublicKeyP))) + { + found = true; + /* Merge denomination key signatures of downloaded /keys into existing + auditor information 'aix'. */ + GNUNET_array_grow (aix->denom_keys, + aix->num_denom_keys, + aix->num_denom_keys + ai.num_denom_keys); + memcpy (&aix->denom_keys[aix->num_denom_keys - ai.num_denom_keys], + ai.denom_keys, + ai.num_denom_keys * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); + break; + } + } + if (found) + continue; /* we are done */ + if (key_data->auditors_size == key_data->num_auditors) + GNUNET_array_grow (key_data->auditors, + key_data->auditors_size, + key_data->auditors_size * 2 + 2); + key_data->auditors[key_data->num_auditors++] = ai; + }; + } + + if (check_sig) + { + struct TALER_ExchangeKeySetPS ks; + + /* Validate signature... */ + ks.purpose.size = htonl (sizeof (ks)); + ks.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET); + ks.list_issue_date = GNUNET_TIME_absolute_hton (key_data->list_issue_date); + GNUNET_CRYPTO_hash_context_finish (hash_context, + &ks.hc); + hash_context = NULL; + EXITIF (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_data, + &pub)); + EXITIF (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_KEY_SET, + &ks.purpose, + &sig.eddsa_signature, + &pub.eddsa_pub)); + } + return GNUNET_OK; + EXITIF_exit: + + if (NULL != hash_context) + GNUNET_CRYPTO_hash_context_abort (hash_context); + return GNUNET_SYSERR; +} + + +/** + * Free key data object. + * + * @param key_data data to free (pointer itself excluded) + */ +static void +free_key_data (struct TALER_EXCHANGE_Keys *key_data) +{ + GNUNET_array_grow (key_data->sign_keys, + key_data->num_sign_keys, + 0); + for (unsigned int i=0;i<key_data->num_denom_keys;i++) + GNUNET_CRYPTO_rsa_public_key_free (key_data->denom_keys[i].key.rsa_public_key); + + GNUNET_array_grow (key_data->denom_keys, + key_data->denom_keys_size, + 0); + for (unsigned int i=0;i<key_data->num_auditors;i++) + { + GNUNET_array_grow (key_data->auditors[i].denom_keys, + key_data->auditors[i].num_denom_keys, + 0); + GNUNET_free (key_data->auditors[i].auditor_url); + } + GNUNET_array_grow (key_data->auditors, + key_data->auditors_size, + 0); + GNUNET_free_non_null (key_data->version); + key_data->version = NULL; +} + + +/** + * Initiate download of /keys from the exchange. + * + * @param cls exchange where to download /keys from + */ +static void +request_keys (void *cls); + + +/** + * Check if our current response for /keys is valid, and if + * not trigger download. + * + * @param exchange exchange to check keys for + * @param force_download #GNUNET_YES to force download even if /keys is still valid + * @return until when the response is current, 0 if we are re-downloading + */ +struct GNUNET_TIME_Absolute +TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange, + int force_download) +{ + if (NULL != exchange->kr) + return GNUNET_TIME_UNIT_ZERO_ABS; + if ( (GNUNET_NO == force_download) && + (0 < GNUNET_TIME_absolute_get_remaining (exchange->key_data_expiration).rel_value_us) ) + return exchange->key_data_expiration; + if (NULL == exchange->retry_task) + exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys, + exchange); + return GNUNET_TIME_UNIT_ZERO_ABS; +} + + +/** + * Callback used when downloading the reply to a /keys request + * is complete. + * + * @param cls the `struct KeysRequest` + * @param response_code HTTP response code, 0 on error + * @param resp_obj parsed JSON result, NULL on error + */ +static void +keys_completed_cb (void *cls, + long response_code, + const void *resp_obj) +{ + struct KeysRequest *kr = cls; + struct TALER_EXCHANGE_Handle *exchange = kr->exchange; + struct TALER_EXCHANGE_Keys kd; + struct TALER_EXCHANGE_Keys kd_old; + enum TALER_EXCHANGE_VersionCompatibility vc; + const json_t *j = resp_obj; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received keys from URL `%s' with status %ld.\n", + kr->url, + response_code); + kd_old = exchange->key_data; + memset (&kd, + 0, + sizeof (struct TALER_EXCHANGE_Keys)); + vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR; + switch (response_code) + { + case 0: + free_keys_request (kr); + exchange->kr = NULL; + GNUNET_assert (NULL == exchange->retry_task); + exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay); + exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay, + &request_keys, + exchange); + return; + case MHD_HTTP_OK: + if (NULL == j) + { + response_code = 0; + break; + } + /* We keep the denomination keys and auditor signatures from the + previous iteration (/keys cherry picking) */ + kd.num_denom_keys = kd_old.num_denom_keys; + kd.last_denom_issue_date = kd_old.last_denom_issue_date; + GNUNET_array_grow (kd.denom_keys, + kd.denom_keys_size, + kd.num_denom_keys); + + /* First make a shallow copy, we then need another pass for the RSA key... */ + memcpy (kd.denom_keys, + kd_old.denom_keys, + kd_old.num_denom_keys * sizeof (struct TALER_EXCHANGE_DenomPublicKey)); + for (unsigned int i=0;i<kd_old.num_denom_keys;i++) + kd.denom_keys[i].key.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_dup (kd_old.denom_keys[i].key.rsa_public_key); + + kd.num_auditors = kd_old.num_auditors; + kd.auditors = GNUNET_new_array (kd.num_auditors, + struct TALER_EXCHANGE_AuditorInformation); + /* Now the necessary deep copy... */ + for (unsigned int i=0;i<kd_old.num_auditors;i++) + { + const struct TALER_EXCHANGE_AuditorInformation *aold = &kd_old.auditors[i]; + struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i]; + + anew->auditor_pub = aold->auditor_pub; + anew->auditor_url = GNUNET_strdup (aold->auditor_url); + GNUNET_array_grow (anew->denom_keys, + anew->num_denom_keys, + aold->num_denom_keys); + memcpy (anew->denom_keys, + aold->denom_keys, + aold->num_denom_keys * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); + } + + if (GNUNET_OK != + decode_keys_json (j, + GNUNET_YES, + &kd, + &vc)) + { + TALER_LOG_ERROR ("Could not decode /keys response\n"); + response_code = 0; + for (unsigned int i=0;i<kd.num_auditors;i++) + { + struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i]; + + GNUNET_array_grow (anew->denom_keys, + anew->num_denom_keys, + 0); + GNUNET_free (anew->auditor_url); + } + GNUNET_free (kd.auditors); + kd.auditors = NULL; + kd.num_auditors = 0; + for (unsigned int i=0;i<kd_old.num_denom_keys;i++) + GNUNET_CRYPTO_rsa_public_key_free (kd.denom_keys[i].key.rsa_public_key); + GNUNET_array_grow (kd.denom_keys, + kd.denom_keys_size, + 0); + kd.num_denom_keys = 0; + break; + } + json_decref (exchange->key_data_raw); + exchange->key_data_raw = json_deep_copy (j); + exchange->retry_delay = GNUNET_TIME_UNIT_ZERO; + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + break; + } + exchange->key_data = kd; + TALER_LOG_DEBUG ("Last DK issue date update to: %s\n", + GNUNET_STRINGS_absolute_time_to_string + (exchange->key_data.last_denom_issue_date)); + + + if (MHD_HTTP_OK != response_code) + { + exchange->kr = NULL; + free_keys_request (kr); + exchange->state = MHS_FAILED; + if (NULL != exchange->key_data_raw) + { + json_decref (exchange->key_data_raw); + exchange->key_data_raw = NULL; + } + free_key_data (&kd_old); + /* notify application that we failed */ + exchange->cert_cb (exchange->cert_cb_cls, + NULL, + vc); + return; + } + + exchange->kr = NULL; + exchange->key_data_expiration = kr->expire; + free_keys_request (kr); + exchange->state = MHS_CERT; + /* notify application about the key information */ + exchange->cert_cb (exchange->cert_cb_cls, + &exchange->key_data, + vc); + free_key_data (&kd_old); +} + + +/* ********************* library internal API ********* */ + + +/** + * Get the context of a exchange. + * + * @param h the exchange handle to query + * @return ctx context to execute jobs in + */ +struct GNUNET_CURL_Context * +TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h) +{ + return h->ctx; +} + + +/** + * Check if the handle is ready to process requests. + * + * @param h the exchange handle to query + * @return #GNUNET_YES if we are ready, #GNUNET_NO if not + */ +int +TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h) +{ + return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO; +} + + +/** + * Obtain the URL to use for an API request. + * + * @param h handle for the exchange + * @param path Taler API path (i.e. "/reserve/withdraw") + * @return the full URL to use with cURL + */ +char * +TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h, + const char *path) +{ + return TEAH_path_to_url2 (h->url, + path); +} + + +/** + * Obtain the URL to use for an API request. + * + * @param base_url base URL of the exchange (i.e. "http://exchange/") + * @param path Taler API path (i.e. "/reserve/withdraw") + * @return the full URL to use with cURL + */ +char * +TEAH_path_to_url2 (const char *base_url, + const char *path) +{ + char *url; + + if ( ('/' == path[0]) && + (0 < strlen (base_url)) && + ('/' == base_url[strlen (base_url) - 1]) ) + path++; /* avoid generating URL with "//" from concat */ + GNUNET_asprintf (&url, + "%s%s", + base_url, + path); + return url; +} + + +/** + * Parse HTTP timestamp. + * + * @param date header to parse header + * @param at where to write the result + * @return #GNUNET_OK on success + */ +static int +parse_date_string (const char *date, + struct GNUNET_TIME_Absolute *at) +{ + struct tm now; + time_t t; + const char *end; + + memset (&now, + 0, + sizeof (now)); + end = strptime (date, + "%a, %d %b %Y %H:%M:%S %Z", /* RFC-1123 standard spec */ + &now); + if ( (NULL == end) || + ( (*end != '\n') && + (*end != '\r') ) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + t = mktime (&now); + if (((time_t) -1) == t) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "mktime"); + return GNUNET_SYSERR; + } + if (t < 0) + t = 0; /* can happen due to timezone issues if date was 1.1.1970 */ + at->abs_value_us = 1000LL * 1000LL * t; + return GNUNET_OK; +} + + +/** + * Function called for each header in the HTTP /keys response. + * Finds the "Expire:" header and parses it, storing the result + * in the "expire" field fo the keys request. + * + * @param buffer header data received + * @param size size of an item in @a buffer + * @param nitems number of items in @a buffer + * @param userdata the `struct KeysRequest` + * @return `size * nitems` on success (everything else aborts) + */ +static size_t +header_cb (char *buffer, + size_t size, + size_t nitems, + void *userdata) +{ + struct KeysRequest *kr = userdata; + size_t total = size * nitems; + char *val; + + if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": ")) + return total; + if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ", + buffer, + strlen (MHD_HTTP_HEADER_EXPIRES ": "))) + return total; + val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")], + total - strlen (MHD_HTTP_HEADER_EXPIRES ": ")); + if (GNUNET_OK != + parse_date_string (val, + &kr->expire)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s-header `%s'\n", + MHD_HTTP_HEADER_EXPIRES, + val); + kr->expire = GNUNET_TIME_UNIT_ZERO_ABS; + } + GNUNET_free (val); + return total; +} + + +/* ********************* public API ******************* */ + + +/** + * Deserialize the key data and use it to bootstrap @a exchange to + * more efficiently recover the state. Errors in @a data must be + * tolerated (i.e. by re-downloading instead). + * + * @param exchange which exchange's key and wire data should be deserialized + * @return data the data to deserialize + */ +static void +deserialize_data (struct TALER_EXCHANGE_Handle *exchange, + const json_t *data) +{ + enum TALER_EXCHANGE_VersionCompatibility vc; + json_t *keys; + const char *url; + uint32_t version; + struct GNUNET_TIME_Absolute expire; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ("version", + &version), + GNUNET_JSON_spec_json ("keys", + &keys), + GNUNET_JSON_spec_string ("url", + &url), + GNUNET_JSON_spec_absolute_time ("expire", + &expire), + GNUNET_JSON_spec_end() + }; + struct TALER_EXCHANGE_Keys key_data; + + if (NULL == data) + return; + if (GNUNET_OK != + GNUNET_JSON_parse (data, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return; + } + if (0 != version) + return; /* unsupported version */ + if (0 != strcmp (url, + exchange->url)) + { + GNUNET_break (0); + return; + } + memset (&key_data, + 0, + sizeof (struct TALER_EXCHANGE_Keys)); + if (GNUNET_OK != + decode_keys_json (keys, + GNUNET_NO, + &key_data, + &vc)) + { + GNUNET_break (0); + return; + } + /* decode successful, initialize with the result */ + GNUNET_assert (NULL == exchange->key_data_raw); + exchange->key_data_raw = json_deep_copy (keys); + exchange->key_data = key_data; + exchange->key_data_expiration = expire; + exchange->state = MHS_CERT; + /* notify application about the key information */ + exchange->cert_cb (exchange->cert_cb_cls, + &exchange->key_data, + vc); +} + + +/** + * Serialize the latest key data from @a exchange to be persisted on + * disk (to be used with #TALER_EXCHANGE_OPTION_DATA to more + * efficiently recover the state). + * + * @param exchange which exchange's key and wire data should be + * serialized + * @return NULL on error (i.e. no current data available); + * otherwise JSON object owned by the caller + */ +json_t * +TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange) +{ + const struct TALER_EXCHANGE_Keys *kd = &exchange->key_data; + struct GNUNET_TIME_Absolute now; + json_t *keys; + json_t *signkeys; + json_t *denoms; + json_t *auditors; + + now = GNUNET_TIME_absolute_get (); + signkeys = json_array (); + for (unsigned int i=0;i<kd->num_sign_keys;i++) + { + const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i]; + json_t *signkey; + + if (now.abs_value_us > sk->valid_until.abs_value_us) + continue; /* skip keys that have expired */ + signkey = json_pack ("{s:o, s:o, s:o, s:o, s:o}", + "key", + GNUNET_JSON_from_data_auto (&sk->key), + "master_sig", + GNUNET_JSON_from_data_auto (&sk->master_sig), + "stamp_start", + GNUNET_JSON_from_time_abs (sk->valid_from), + "stamp_expire", + GNUNET_JSON_from_time_abs (sk->valid_until), + "stamp_end", + GNUNET_JSON_from_time_abs (sk->valid_legal)); + if (NULL == signkey) + { + GNUNET_break (0); + continue; + } + json_array_append_new (signkeys, + signkey); + } + denoms = json_array (); + for (unsigned int i=0;i<kd->num_denom_keys;i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i]; + json_t *denom; + + if (now.abs_value_us > dk->expire_deposit.abs_value_us) + continue; /* skip keys that have expired */ + denom = json_pack ("{s:o, s:o, s:o, s:o, s:o " + ",s:o, s:o, s:o, s:o, s:o " + ",s:o}", + "stamp_expire_deposit", + GNUNET_JSON_from_time_abs (dk->expire_deposit), + "stamp_expire_withdraw", + GNUNET_JSON_from_time_abs (dk->withdraw_valid_until), + "stamp_start", + GNUNET_JSON_from_time_abs (dk->valid_from), + "stamp_expire_legal", + GNUNET_JSON_from_time_abs (dk->expire_legal), + "value", + TALER_JSON_from_amount (&dk->value), + "fee_withdraw", + /* #6 */ + TALER_JSON_from_amount (&dk->fee_withdraw), + "fee_deposit", + TALER_JSON_from_amount (&dk->fee_deposit), + "fee_refresh", + TALER_JSON_from_amount (&dk->fee_refresh), + "fee_refund", + TALER_JSON_from_amount (&dk->fee_refund), + "master_sig", + GNUNET_JSON_from_data_auto (&dk->master_sig), + /* #10 */ + "denom_pub", + GNUNET_JSON_from_rsa_public_key (dk->key.rsa_public_key)); + if (NULL == denom) + { + GNUNET_break (0); + continue; + } + json_array_append_new (denoms, + denom); + } + auditors = json_array (); + for (unsigned int i=0;i<kd->num_auditors;i++) + { + const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i]; + json_t *a; + json_t *adenoms; + + adenoms = json_array (); + for (unsigned int j=0;j<ai->num_denom_keys;j++) + { + const struct TALER_EXCHANGE_AuditorDenominationInfo *adi = &ai->denom_keys[j]; + const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[adi->denom_key_offset]; + json_t *k; + + if (now.abs_value_us > dk->expire_deposit.abs_value_us) + continue; /* skip auditor signatures for denomination keys that have expired */ + GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys); + k = json_pack ("{s:o, s:o}", + "denom_pub_h", + GNUNET_JSON_from_data_auto (&dk->h_key), + "auditor_sig", + GNUNET_JSON_from_data_auto (&adi->auditor_sig)); + if (NULL == k) + { + GNUNET_break (0); + continue; + } + json_array_append_new (adenoms, + k); + } + + a = json_pack ("{s:o, s:s, s:o}", + "auditor_pub", + GNUNET_JSON_from_data_auto (&ai->auditor_pub), + "auditor_url", + ai->auditor_url, + "denomination_keys", + adenoms); + if (NULL == a) + { + GNUNET_break (0); + continue; + } + json_array_append_new (auditors, + a); + } + keys = json_pack ("{s:s, s:o, s:o, s:o, s:o" + ",s:o, s:o}", + /* 1 */ + "version", + kd->version, + "master_public_key", + GNUNET_JSON_from_data_auto (&kd->master_pub), + "reserve_closing_delay", + GNUNET_JSON_from_time_rel (kd->reserve_closing_delay), + "list_issue_date", + GNUNET_JSON_from_time_abs (kd->list_issue_date), + "signkeys", + signkeys, + /* #6 */ + "denoms", + denoms, + "auditors", + auditors); + if (NULL == keys) + { + GNUNET_break (0); + return NULL; + } + return json_pack ("{s:I, s:o, s:s, s:o}", + "version", + (json_int_t) TALER_SERIALIZATION_FORMAT_VERSION, + "expire", + GNUNET_JSON_from_time_abs (exchange->key_data_expiration), + "url", + exchange->url, + "keys", + keys); +} + + +/** + * Initialise a connection to the exchange. Will connect to the + * exchange and obtain information about the exchange's master + * public key and the exchange's auditor. + * The respective information will be passed to the @a cert_cb + * once available, and all future interactions with the exchange + * will be checked to be signed (where appropriate) by the + * respective master key. + * + * @param ctx the context + * @param url HTTP base URL for the exchange + * @param cert_cb function to call with the exchange's + * certification information + * @param cert_cb_cls closure for @a cert_cb + * @param ... list of additional arguments, + * terminated by #TALER_EXCHANGE_OPTION_END. + * @return the exchange handle; NULL upon error + */ +struct TALER_EXCHANGE_Handle * +TALER_EXCHANGE_connect + (struct GNUNET_CURL_Context *ctx, + const char *url, + TALER_EXCHANGE_CertificationCallback cert_cb, + void *cert_cb_cls, + ...) +{ + struct TALER_EXCHANGE_Handle *exchange; + va_list ap; + enum TALER_EXCHANGE_Option opt; + + exchange = GNUNET_new (struct TALER_EXCHANGE_Handle); + exchange->ctx = ctx; + exchange->url = GNUNET_strdup (url); + exchange->cert_cb = cert_cb; + exchange->cert_cb_cls = cert_cb_cls; + exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys, + exchange); + va_start (ap, cert_cb_cls); + while (TALER_EXCHANGE_OPTION_END != + (opt = va_arg (ap, int))) + { + switch (opt) { + case TALER_EXCHANGE_OPTION_END: + GNUNET_assert (0); + break; + case TALER_EXCHANGE_OPTION_DATA: + { + const json_t *data = va_arg (ap, const json_t *); + + deserialize_data (exchange, + data); + break; + } + default: + GNUNET_assert (0); + break; + } + } + va_end (ap); + return exchange; +} + + +/** + * Initiate download of /keys from the exchange. + * + * @param cls exchange where to download /keys from + */ +static void +request_keys (void *cls) +{ + struct TALER_EXCHANGE_Handle *exchange = cls; + struct KeysRequest *kr; + CURL *eh; + + exchange->retry_task = NULL; + GNUNET_assert (NULL == exchange->kr); + kr = GNUNET_new (struct KeysRequest); + kr->exchange = exchange; + if (GNUNET_YES == + TEAH_handle_is_ready (exchange)) + { + char *arg; + + TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n", + GNUNET_STRINGS_absolute_time_to_string (exchange->key_data.last_denom_issue_date)); + GNUNET_asprintf (&arg, + "/keys?last_issue_date=%llu", + (unsigned long long) exchange->key_data.last_denom_issue_date.abs_value_us / 1000000LLU); + kr->url = TEAH_path_to_url (exchange, + arg); + GNUNET_free (arg); + } + else + { + kr->url = TEAH_path_to_url (exchange, + "/keys"); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting keys with URL `%s'.\n", + kr->url); + eh = TEL_curl_easy_get (kr->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_VERBOSE, + 0)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT, + (long) 300)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERFUNCTION, + &header_cb)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + kr)); + kr->job = GNUNET_CURL_job_add (exchange->ctx, + eh, + GNUNET_YES, + &keys_completed_cb, + kr); + exchange->kr = kr; +} + + +/** + * Disconnect from the exchange + * + * @param exchange the exchange handle + */ +void +TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange) +{ + if (NULL != exchange->kr) + { + GNUNET_CURL_job_cancel (exchange->kr->job); + free_keys_request (exchange->kr); + exchange->kr = NULL; + } + free_key_data (&exchange->key_data); + if (NULL != exchange->key_data_raw) + { + json_decref (exchange->key_data_raw); + exchange->key_data_raw = NULL; + } + if (NULL != exchange->retry_task) + { + GNUNET_SCHEDULER_cancel (exchange->retry_task); + exchange->retry_task = NULL; + } + GNUNET_free (exchange->url); + GNUNET_free (exchange); +} + + +/** + * Lookup the given @a pub in @a keys. + * + * @param keys the exchange's key set + * @param pub claimed current online signing key for the exchange + * @return NULL if @a pub was not found + */ +const struct TALER_EXCHANGE_SigningPublicKey * +TALER_EXCHANGE_get_signing_key_details (const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ExchangePublicKeyP *pub) +{ + for (unsigned int i=0;i<keys->num_sign_keys;i++) + { + struct TALER_EXCHANGE_SigningPublicKey *spk = &keys->sign_keys[i]; + + if (0 == memcmp (pub, + &spk->key, + sizeof (struct TALER_ExchangePublicKeyP))) + return spk; + } + return NULL; +} + + +/** + * Test if the given @a pub is a the current signing key from the exchange + * according to @a keys. + * + * @param keys the exchange's key set + * @param pub claimed current online signing key for the exchange + * @return #GNUNET_OK if @a pub is (according to /keys) a current signing key + */ +int +TALER_EXCHANGE_test_signing_key (const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ExchangePublicKeyP *pub) +{ + struct GNUNET_TIME_Absolute now; + + /* we will check using a tolerance of 1h for the time */ + now = GNUNET_TIME_absolute_get (); + for (unsigned int i=0;i<keys->num_sign_keys;i++) + if ( (keys->sign_keys[i].valid_from.abs_value_us <= now.abs_value_us + 60 * 60 * 1000LL * 1000LL) && + (keys->sign_keys[i].valid_until.abs_value_us > now.abs_value_us - 60 * 60 * 1000LL * 1000LL) && + (0 == memcmp (pub, + &keys->sign_keys[i].key, + sizeof (struct TALER_ExchangePublicKeyP))) ) + return GNUNET_OK; + return GNUNET_SYSERR; +} + + +/** + * Get exchange's base URL. + * + * @param exchange exchange handle. + * @return the base URL from the handle. + */ +const char * +TALER_EXCHANGE_get_base_url (const struct TALER_EXCHANGE_Handle *exchange) +{ + return exchange->url; +} + + +/** + * Obtain the denomination key details from the exchange. + * + * @param keys the exchange's key set + * @param pk public key of the denomination to lookup + * @return details about the given denomination key, NULL if the key is + * not found + */ +const struct TALER_EXCHANGE_DenomPublicKey * +TALER_EXCHANGE_get_denomination_key (const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_DenominationPublicKey *pk) +{ + for (unsigned int i=0;i<keys->num_denom_keys;i++) + if (0 == GNUNET_CRYPTO_rsa_public_key_cmp (pk->rsa_public_key, + keys->denom_keys[i].key.rsa_public_key)) + return &keys->denom_keys[i]; + return NULL; +} + + +/** + * Obtain the denomination key details from the exchange. + * + * @param keys the exchange's key set + * @param hc hash of the public key of the denomination to lookup + * @return details about the given denomination key + */ +const struct TALER_EXCHANGE_DenomPublicKey * +TALER_EXCHANGE_get_denomination_key_by_hash (const struct TALER_EXCHANGE_Keys *keys, + const struct GNUNET_HashCode *hc) +{ + for (unsigned int i=0;i<keys->num_denom_keys;i++) + if (0 == memcmp (hc, + &keys->denom_keys[i].h_key, + sizeof (struct GNUNET_HashCode))) + return &keys->denom_keys[i]; + return NULL; +} + + +/** + * Obtain the keys from the exchange. + * + * @param exchange the exchange handle + * @return the exchange's key set + */ +const struct TALER_EXCHANGE_Keys * +TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange) +{ + (void) TALER_EXCHANGE_check_keys_current (exchange, + GNUNET_NO); + return &exchange->key_data; +} + + +/** + * Obtain the keys from the exchange in the + * raw JSON format + * + * @param exchange the exchange handle + * @return the exchange's keys in raw JSON + */ +json_t * +TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange) +{ + (void) TALER_EXCHANGE_check_keys_current (exchange, + GNUNET_NO); + return json_deep_copy (exchange->key_data_raw); +} + + +/* end of exchange_api_handle.c */ diff --git a/src/exchange-lib/exchange_api_handle.h b/src/lib/exchange_api_handle.h diff --git a/src/lib/exchange_api_payback.c b/src/lib/exchange_api_payback.c @@ -0,0 +1,374 @@ +/* + This file is part of TALER + Copyright (C) 2017 GNUnet e.V. and Inria + + 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 exchange-lib/exchange_api_payback.c + * @brief Implementation of the /payback 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 Payback Handle + */ +struct TALER_EXCHANGE_PaybackHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Denomination key of the coin. + */ + const struct TALER_EXCHANGE_DenomPublicKey *pk; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_PaybackResultCallback 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; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. If it is, call the + * callback. + * + * @param ph payback 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 int +verify_payback_signature_ok (const struct TALER_EXCHANGE_PaybackHandle *ph, + const json_t *json) +{ + struct TALER_PaybackConfirmationPS pc; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_Amount amount; + struct GNUNET_TIME_Absolute timestamp; + 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), + TALER_JSON_spec_amount ("amount", &amount), + GNUNET_JSON_spec_absolute_time ("timestamp", &timestamp), + GNUNET_JSON_spec_fixed_auto ("reserve_pub", &pc.reserve_pub), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = TALER_EXCHANGE_get_keys (ph->exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + &exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK); + pc.purpose.size = htonl (sizeof (pc)); + pc.timestamp = GNUNET_TIME_absolute_hton (timestamp); + TALER_amount_hton (&pc.payback_amount, + &amount); + pc.coin_pub = ph->coin_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK, + &pc.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + ph->cb (ph->cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + &amount, + timestamp, + &pc.reserve_pub, + json); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /payback request. + * + * @param cls the `struct TALER_EXCHANGE_PaybackHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_payback_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_PaybackHandle *ph = cls; + const json_t *j = response; + + ph->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + verify_payback_signature_ok (ph, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + TALER_EXCHANGE_payback_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 */ + break; + case MHD_HTTP_FORBIDDEN: + { + /* Insufficient funds, proof attached */ + json_t *history; + struct TALER_Amount total; + const struct TALER_EXCHANGE_DenomPublicKey *dki; + + dki = ph->pk; + history = json_object_get (j, + "history"); + if (GNUNET_OK != + TALER_EXCHANGE_verify_coin_history (dki->fee_deposit.currency, + &ph->coin_pub, + history, + &total)) + { + GNUNET_break_op (0); + response_code = 0; + } + ph->cb (ph->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + &total, + GNUNET_TIME_UNIT_FOREVER_ABS, + NULL, + j); + TALER_EXCHANGE_payback_cancel (ph); + return; + } + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_GONE: + /* Kind of normal: the money was already sent to the merchant + (it was too late for the refund). */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + ph->cb (ph->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + NULL, + GNUNET_TIME_UNIT_FOREVER_ABS, + NULL, + j); + TALER_EXCHANGE_payback_cancel (ph); +} + + +/** + * Ask the exchange to pay back a coin due to the exchange triggering + * the emergency payback protocol for a given denomination. The value + * of the coin will be refunded to the original customer (without fees). + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param pk kind of coin to pay back + * @param denom_sig signature over the coin by the exchange using @a pk + * @param ps secret internals of the original planchet + * @param payback_cb the callback to call when the final result for this request is available + * @param payback_cb_cls closure for @a payback_cb + * @return NULL + * if the inputs are invalid (i.e. denomination key not with this exchange). + * In this case, the callback is not called. + */ +struct TALER_EXCHANGE_PaybackHandle * +TALER_EXCHANGE_payback (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_EXCHANGE_DenomPublicKey *pk, + const struct TALER_DenominationSignature *denom_sig, + const struct TALER_PlanchetSecretsP *ps, + TALER_EXCHANGE_PaybackResultCallback payback_cb, + void *payback_cb_cls) +{ + struct TALER_EXCHANGE_PaybackHandle *ph; + struct GNUNET_CURL_Context *ctx; + struct TALER_PaybackRequestPS pr; + struct TALER_CoinSpendSignatureP coin_sig; + json_t *payback_obj; + CURL *eh; + + GNUNET_assert (GNUNET_YES == + TEAH_handle_is_ready (exchange)); + pr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_PAYBACK); + pr.purpose.size = htonl (sizeof (struct TALER_PaybackRequestPS)); + GNUNET_CRYPTO_eddsa_key_get_public (&ps->coin_priv.eddsa_priv, + &pr.coin_pub.eddsa_pub); + pr.h_denom_pub = pk->h_key; + pr.coin_blind = ps->blinding_key; + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&ps->coin_priv.eddsa_priv, + &pr.purpose, + &coin_sig.eddsa_signature)); + + payback_obj = json_pack ("{s:o, s:o," /* denom pub/sig */ + " s:o, s:o," /* coin pub/sig */ + " s:o}", /* coin_bks */ + "denom_pub", GNUNET_JSON_from_rsa_public_key (pk->key.rsa_public_key), + "denom_sig", GNUNET_JSON_from_rsa_signature (denom_sig->rsa_signature), + "coin_pub", GNUNET_JSON_from_data_auto (&pr.coin_pub), + "coin_sig", GNUNET_JSON_from_data_auto (&coin_sig), + "coin_blind_key_secret", GNUNET_JSON_from_data_auto (&ps->blinding_key) + ); + if (NULL == payback_obj) + { + GNUNET_break (0); + return NULL; + } + + ph = GNUNET_new (struct TALER_EXCHANGE_PaybackHandle); + ph->coin_pub = pr.coin_pub; + ph->exchange = exchange; + ph->pk = pk; + ph->cb = payback_cb; + ph->cb_cls = payback_cb_cls; + ph->url = TEAH_path_to_url (exchange, "/payback"); + + ph->json_enc = json_dumps (payback_obj, + JSON_COMPACT); + json_decref (payback_obj); + if (NULL == ph->json_enc) + { + GNUNET_break (0); + GNUNET_free (ph->url); + GNUNET_free (ph); + return NULL; + } + eh = TEL_curl_easy_get (ph->url); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for payback: `%s'\n", + ph->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + ph->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (ph->json_enc))); + ctx = TEAH_handle_to_context (exchange); + ph->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_payback_finished, + ph); + return ph; +} + + +/** + * Cancel a payback request. This function cannot be used on a + * request handle if the callback was already invoked. + * + * @param ph the payback handle + */ +void +TALER_EXCHANGE_payback_cancel (struct TALER_EXCHANGE_PaybackHandle *ph) +{ + if (NULL != ph->job) + { + GNUNET_CURL_job_cancel (ph->job); + ph->job = NULL; + } + GNUNET_free (ph->url); + GNUNET_free (ph->json_enc); + GNUNET_free (ph); +} + + +/* end of exchange_api_payback.c */ diff --git a/src/lib/exchange_api_refresh.c b/src/lib/exchange_api_refresh.c @@ -0,0 +1,1678 @@ +/* + This file is part of TALER + Copyright (C) 2015, 2016, 2017 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 exchange-lib/exchange_api_refresh.c + * @brief Implementation of the /refresh/melt+reveal requests 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" + + +/* ********************* /refresh/ common ***************************** */ + +/* structures for committing refresh data to disk before doing the + network interaction(s) */ + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Header of serialized information about a coin we are melting. + */ +struct MeltedCoinP +{ + /** + * Private key of the coin. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + + /** + * Amount this coin contributes to the melt, including fee. + */ + struct TALER_AmountNBO melt_amount_with_fee; + + /** + * The applicable fee for withdrawing a coin of this denomination + */ + struct TALER_AmountNBO fee_melt; + + /** + * The original value of the coin. + */ + struct TALER_AmountNBO original_value; + + /** + * Transfer private keys for each cut-and-choose dimension. + */ + struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA]; + + /** + * Timestamp indicating when coins of this denomination become invalid. + */ + struct GNUNET_TIME_AbsoluteNBO expire_deposit; + + /** + * Size of the encoded public key that follows. + */ + uint16_t pbuf_size; + + /** + * Size of the encoded signature that follows. + */ + uint16_t sbuf_size; + + /* Followed by serializations of: + 1) struct TALER_DenominationPublicKey pub_key; + 2) struct TALER_DenominationSignature sig; + */ +}; + + +/** + * Header of serialized data about a melt operation, suitable for + * persisting it on disk. + */ +struct MeltDataP +{ + + /** + * Hash over the melting session. + */ + struct TALER_RefreshCommitmentP rc; + + /** + * Number of coins we are melting, in NBO + */ + uint16_t num_melted_coins GNUNET_PACKED; + + /** + * Number of coins we are creating, in NBO + */ + uint16_t num_fresh_coins GNUNET_PACKED; + + /* Followed by serializations of: + 1) struct MeltedCoinP melted_coins[num_melted_coins]; + 2) struct TALER_EXCHANGE_DenomPublicKey fresh_pks[num_fresh_coins]; + 3) TALER_CNC_KAPPA times: + 3a) struct TALER_PlanchetSecretsP fresh_coins[num_fresh_coins]; + */ +}; + + +GNUNET_NETWORK_STRUCT_END + + +/** + * 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; + + /** + * Transfer private keys for each cut-and-choose dimension. + */ + struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA]; + + /** + * Timestamp indicating when coins of this denomination become invalid. + */ + struct GNUNET_TIME_Absolute expire_deposit; + + /** + * Denomination key of the original coin. + */ + struct TALER_DenominationPublicKey pub_key; + + /** + * Exchange's signature over the coin. + */ + struct TALER_DenominationSignature sig; + +}; + + +/** + * Melt data in non-serialized format for convenient processing. + */ +struct MeltData +{ + + /** + * Hash over the committed data during refresh operation. + */ + struct TALER_RefreshCommitmentP rc; + + /** + * Number of coins we are creating + */ + uint16_t num_fresh_coins; + + /** + * Information about the melted coin. + */ + struct MeltedCoin melted_coin; + + /** + * Array of @e num_fresh_coins denomination keys for the coins to be + * freshly exchangeed. + */ + struct TALER_DenominationPublicKey *fresh_pks; + + /** + * Arrays of @e num_fresh_coins with information about the fresh + * coins to be created, for each cut-and-choose dimension. + */ + struct TALER_PlanchetSecretsP *fresh_coins[TALER_CNC_KAPPA]; +}; + + +/** + * Free all information associated with a melted coin session. + * + * @param mc melted coin to release, the pointer itself is NOT + * freed (as it is typically not allocated by itself) + */ +static void +free_melted_coin (struct MeltedCoin *mc) +{ + if (NULL != mc->pub_key.rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (mc->pub_key.rsa_public_key); + if (NULL != mc->sig.rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (mc->sig.rsa_signature); +} + + +/** + * 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 (i.e. due to failures in #deserialize_melt_data()). + * + * @param md melting data to release, the pointer itself is NOT + * freed (as it is typically not allocated by itself) + */ +static void +free_melt_data (struct MeltData *md) +{ + free_melted_coin (&md->melted_coin); + if (NULL != md->fresh_pks) + { + for (unsigned int i=0;i<md->num_fresh_coins;i++) + if (NULL != md->fresh_pks[i].rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (md->fresh_pks[i].rsa_public_key); + GNUNET_free (md->fresh_pks); + } + + for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) + GNUNET_free (md->fresh_coins[i]); + /* Finally, clean up a bit... + (NOTE: compilers might optimize this away, so this is + not providing any strong assurances that the key material + is purged.) */ + memset (md, + 0, + sizeof (struct MeltData)); +} + + +/** + * Serialize information about a coin we are melting. + * + * @param mc information to serialize + * @param buf buffer to write data in, NULL to just compute + * required size + * @param off offeset at @a buf to use + * @return number of bytes written to @a buf at @a off, or if + * @a buf is NULL, number of bytes required; 0 on error + */ +static size_t +serialize_melted_coin (const struct MeltedCoin *mc, + char *buf, + size_t off) +{ + struct MeltedCoinP mcp; + unsigned int i; + char *pbuf; + size_t pbuf_size; + char *sbuf; + size_t sbuf_size; + + sbuf_size = GNUNET_CRYPTO_rsa_signature_encode (mc->sig.rsa_signature, + &sbuf); + pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (mc->pub_key.rsa_public_key, + &pbuf); + if (NULL == buf) + { + GNUNET_free (sbuf); + GNUNET_free (pbuf); + return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size; + } + if ( (sbuf_size > UINT16_MAX) || + (pbuf_size > UINT16_MAX) ) + { + GNUNET_break (0); + return 0; + } + mcp.coin_priv = mc->coin_priv; + TALER_amount_hton (&mcp.melt_amount_with_fee, + &mc->melt_amount_with_fee); + TALER_amount_hton (&mcp.fee_melt, + &mc->fee_melt); + TALER_amount_hton (&mcp.original_value, + &mc->original_value); + for (i=0;i<TALER_CNC_KAPPA;i++) + mcp.transfer_priv[i] = mc->transfer_priv[i]; + mcp.expire_deposit = GNUNET_TIME_absolute_hton (mc->expire_deposit); + mcp.pbuf_size = htons ((uint16_t) pbuf_size); + mcp.sbuf_size = htons ((uint16_t) sbuf_size); + memcpy (&buf[off], + &mcp, + sizeof (struct MeltedCoinP)); + memcpy (&buf[off + sizeof (struct MeltedCoinP)], + pbuf, + pbuf_size); + memcpy (&buf[off + sizeof (struct MeltedCoinP) + pbuf_size], + sbuf, + sbuf_size); + GNUNET_free (sbuf); + GNUNET_free (pbuf); + return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size; +} + + +/** + * Deserialize information about a coin we are melting. + * + * @param[out] mc information to deserialize + * @param buf buffer to read data from + * @param size number of bytes available at @a buf to use + * @param[out] ok set to #GNUNET_NO to report errors + * @return number of bytes read from @a buf, 0 on error + */ +static size_t +deserialize_melted_coin (struct MeltedCoin *mc, + const char *buf, + size_t size, + int *ok) +{ + struct MeltedCoinP mcp; + unsigned int i; + size_t pbuf_size; + size_t sbuf_size; + size_t off; + + if (size < sizeof (struct MeltedCoinP)) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + memcpy (&mcp, + buf, + sizeof (struct MeltedCoinP)); + pbuf_size = ntohs (mcp.pbuf_size); + sbuf_size = ntohs (mcp.sbuf_size); + if (size < sizeof (struct MeltedCoinP) + pbuf_size + sbuf_size) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + off = sizeof (struct MeltedCoinP); + mc->pub_key.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_decode (&buf[off], + pbuf_size); + off += pbuf_size; + mc->sig.rsa_signature + = GNUNET_CRYPTO_rsa_signature_decode (&buf[off], + sbuf_size); + off += sbuf_size; + if ( (NULL == mc->pub_key.rsa_public_key) || + (NULL == mc->sig.rsa_signature) ) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + + mc->coin_priv = mcp.coin_priv; + TALER_amount_ntoh (&mc->melt_amount_with_fee, + &mcp.melt_amount_with_fee); + TALER_amount_ntoh (&mc->fee_melt, + &mcp.fee_melt); + TALER_amount_ntoh (&mc->original_value, + &mcp.original_value); + for (i=0;i<TALER_CNC_KAPPA;i++) + mc->transfer_priv[i] = mcp.transfer_priv[i]; + mc->expire_deposit = GNUNET_TIME_absolute_ntoh (mcp.expire_deposit); + return off; +} + + +/** + * Serialize information about a denomination key. + * + * @param dk information to serialize + * @param buf buffer to write data in, NULL to just compute + * required size + * @param off offeset at @a buf to use + * @return number of bytes written to @a buf at @a off, or if + * @a buf is NULL, number of bytes required + */ +static size_t +serialize_denomination_key (const struct TALER_DenominationPublicKey *dk, + char *buf, + size_t off) +{ + char *pbuf; + size_t pbuf_size; + uint32_t be; + + pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (dk->rsa_public_key, + &pbuf); + if (NULL == buf) + { + GNUNET_free (pbuf); + return pbuf_size + sizeof (uint32_t); + } + be = htonl ((uint32_t) pbuf_size); + memcpy (&buf[off], + &be, + sizeof (uint32_t)); + memcpy (&buf[off + sizeof (uint32_t)], + pbuf, + pbuf_size); + GNUNET_free (pbuf); + return pbuf_size + sizeof (uint32_t); +} + + +/** + * Deserialize information about a denomination key. + * + * @param[out] dk information to deserialize + * @param buf buffer to read data from + * @param size number of bytes available at @a buf to use + * @param[out] ok set to #GNUNET_NO to report errors + * @return number of bytes read from @a buf, 0 on error + */ +static size_t +deserialize_denomination_key (struct TALER_DenominationPublicKey *dk, + const char *buf, + size_t size, + int *ok) +{ + size_t pbuf_size; + uint32_t be; + + if (size < sizeof (uint32_t)) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + memcpy (&be, + buf, + sizeof (uint32_t)); + pbuf_size = ntohl (be); + if (size < sizeof (uint32_t) + pbuf_size) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + dk->rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_decode (&buf[sizeof (uint32_t)], + pbuf_size); + if (NULL == dk->rsa_public_key) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + return sizeof (uint32_t) + pbuf_size; +} + + +/** + * Serialize information about a fresh coin we are generating. + * + * @param fc information to serialize + * @param buf buffer to write data in, NULL to just compute + * required size + * @param off offeset at @a buf to use + * @return number of bytes written to @a buf at @a off, or if + * @a buf is NULL, number of bytes required + */ +static size_t +serialize_fresh_coin (const struct TALER_PlanchetSecretsP *fc, + char *buf, + size_t off) +{ + if (NULL != buf) + memcpy (&buf[off], + fc, + sizeof (struct TALER_PlanchetSecretsP)); + return sizeof (struct TALER_PlanchetSecretsP); +} + + +/** + * Deserialize information about a fresh coin we are generating. + * + * @param[out] fc information to deserialize + * @param buf buffer to read data from + * @param size number of bytes available at @a buf to use + * @param[out] ok set to #GNUNET_NO to report errors + * @return number of bytes read from @a buf, 0 on error + */ +static size_t +deserialize_fresh_coin (struct TALER_PlanchetSecretsP *fc, + const char *buf, + size_t size, + int *ok) +{ + if (size < sizeof (struct TALER_PlanchetSecretsP)) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + memcpy (fc, + buf, + sizeof (struct TALER_PlanchetSecretsP)); + return sizeof (struct TALER_PlanchetSecretsP); +} + + +/** + * Serialize melt data. + * + * @param md data to serialize + * @param[out] res_size size of buffer returned + * @return serialized melt data + */ +static char * +serialize_melt_data (const struct MeltData *md, + size_t *res_size) +{ + size_t size; + size_t asize; + char *buf; + + size = 0; + asize = (size_t) -1; /* make the compiler happy */ + buf = NULL; + /* we do 2 iterations, #1 to determine total size, #2 to + actually construct the buffer */ + do { + if (0 == size) + { + size = sizeof (struct MeltDataP); + } + else + { + struct MeltDataP *mdp; + + buf = GNUNET_malloc (size); + asize = size; /* just for invariant check later */ + size = sizeof (struct MeltDataP); + mdp = (struct MeltDataP *) buf; + mdp->rc = md->rc; + mdp->num_fresh_coins = htons (md->num_fresh_coins); + } + size += serialize_melted_coin (&md->melted_coin, + buf, + size); + for (unsigned int i=0;i<md->num_fresh_coins;i++) + size += serialize_denomination_key (&md->fresh_pks[i], + buf, + size); + for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) + for(unsigned int j=0;j<md->num_fresh_coins;j++) + size += serialize_fresh_coin (&md->fresh_coins[i][j], + buf, + size); + } while (NULL == buf); + GNUNET_assert (size == asize); + *res_size = size; + return buf; +} + + +/** + * Deserialize melt data. + * + * @param buf serialized data + * @param buf_size size of @a buf + * @return deserialized melt data, NULL on error + */ +static struct MeltData * +deserialize_melt_data (const char *buf, + size_t buf_size) +{ + struct MeltData *md; + struct MeltDataP mdp; + size_t off; + int ok; + + if (buf_size < sizeof (struct MeltDataP)) + return NULL; + memcpy (&mdp, + buf, + sizeof (struct MeltDataP)); + md = GNUNET_new (struct MeltData); + md->rc = mdp.rc; + md->num_fresh_coins = ntohs (mdp.num_fresh_coins); + md->fresh_pks = GNUNET_new_array (md->num_fresh_coins, + struct TALER_DenominationPublicKey); + for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) + md->fresh_coins[i] = GNUNET_new_array (md->num_fresh_coins, + struct TALER_PlanchetSecretsP); + off = sizeof (struct MeltDataP); + ok = GNUNET_YES; + off += deserialize_melted_coin (&md->melted_coin, + &buf[off], + buf_size - off, + &ok); + for (unsigned int i=0;(i<md->num_fresh_coins)&&(GNUNET_YES == ok);i++) + off += deserialize_denomination_key (&md->fresh_pks[i], + &buf[off], + buf_size - off, + &ok); + + for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) + for (unsigned int j=0;(j<md->num_fresh_coins)&&(GNUNET_YES == ok);j++) + off += deserialize_fresh_coin (&md->fresh_coins[i][j], + &buf[off], + buf_size - off, + &ok); + if (off != buf_size) + { + GNUNET_break (0); + ok = GNUNET_NO; + } + if (GNUNET_YES != ok) + { + free_melt_data (md); + GNUNET_free (md); + return NULL; + } + return md; +} + + +/** + * Melt (partially spent) coins to obtain fresh coins that are + * unlinkable to the original coin(s). Note that melting more + * than one coin in a single request will make those coins linkable, + * so the safest operation only melts one coin at a time. + * + * This API is typically used by a wallet. Note that to ensure that + * no money is lost in case of hardware failures, is operation does + * not actually initiate the request. Instead, it generates a buffer + * which the caller must store before proceeding with the actual call + * to #TALER_EXCHANGE_refresh_melt() that will generate the request. + * + * This function does verify that the given request data is internally + * consistent. However, the @a melts_sigs are only verified if + * @a check_sigs is set to #GNUNET_YES, as this may be relatively + * expensive and should be redundant. + * + * Aside from some non-trivial cryptographic operations that might + * take a bit of CPU time to complete, this function returns + * its result immediately and does not start any asynchronous + * processing. This function is also thread-safe. + * + * @param melt_priv private key of the coin to melt + * @param melt_amount amount specifying how much + * the coin will contribute to the melt (including fee) + * @param melt_sig signature affirming the + * validity of the public keys corresponding to the + * @a melt_priv private key + * @param melt_pk denomination key information + * record corresponding to the @a melt_sig + * validity of the keys + * @param check_sig verify the validity of the @a melt_sig signature + * @param fresh_pks_len length of the @a pks array + * @param fresh_pks array of @a pks_len denominations of fresh coins to create + * @param[out] res_size set to the size of the return value, or 0 on error + * @return NULL + * if the inputs are invalid (i.e. denomination key not with this exchange). + * Otherwise, pointer to a buffer of @a res_size to store persistently + * before proceeding to #TALER_EXCHANGE_refresh_melt(). + * Non-null results should be freed using GNUNET_free(). + */ +char * +TALER_EXCHANGE_refresh_prepare (const struct TALER_CoinSpendPrivateKeyP *melt_priv, + const struct TALER_Amount *melt_amount, + const struct TALER_DenominationSignature *melt_sig, + const struct TALER_EXCHANGE_DenomPublicKey *melt_pk, + int check_sig, + unsigned int fresh_pks_len, + const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks, + size_t *res_size) +{ + struct MeltData md; + char *buf; + struct TALER_Amount total; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA]; + struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA]; + + GNUNET_CRYPTO_eddsa_key_get_public (&melt_priv->eddsa_priv, + &coin_pub.eddsa_pub); + /* build up melt data structure */ + md.num_fresh_coins = fresh_pks_len; + md.melted_coin.coin_priv = *melt_priv; + md.melted_coin.melt_amount_with_fee = *melt_amount; + md.melted_coin.fee_melt = melt_pk->fee_refresh; + md.melted_coin.original_value = melt_pk->value; + md.melted_coin.expire_deposit + = melt_pk->expire_deposit; + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (melt_amount->currency, + &total)); + md.melted_coin.pub_key.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_dup (melt_pk->key.rsa_public_key); + md.melted_coin.sig.rsa_signature + = GNUNET_CRYPTO_rsa_signature_dup (melt_sig->rsa_signature); + md.fresh_pks = GNUNET_new_array (fresh_pks_len, + struct TALER_DenominationPublicKey); + for (unsigned int i=0;i<fresh_pks_len;i++) + { + md.fresh_pks[i].rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_dup (fresh_pks[i].key.rsa_public_key); + if ( (GNUNET_OK != + TALER_amount_add (&total, + &total, + &fresh_pks[i].value)) || + (GNUNET_OK != + TALER_amount_add (&total, + &total, + &fresh_pks[i].fee_withdraw)) ) + { + GNUNET_break (0); + free_melt_data (&md); + return NULL; + } + } + /* verify that melt_amount is above total cost */ + if (1 == + TALER_amount_cmp (&total, + melt_amount) ) + { + /* Eh, this operation is more expensive than the + @a melt_amount. This is not OK. */ + GNUNET_break (0); + free_melt_data (&md); + return NULL; + } + + /* build up coins */ + for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) + { + struct GNUNET_CRYPTO_EcdhePrivateKey *tpk; + + tpk = GNUNET_CRYPTO_ecdhe_key_create (); + md.melted_coin.transfer_priv[i].ecdhe_priv = *tpk; + GNUNET_free (tpk); + + GNUNET_CRYPTO_ecdhe_key_get_public (&md.melted_coin.transfer_priv[i].ecdhe_priv, + &rce[i].transfer_pub.ecdhe_pub); + TALER_link_derive_transfer_secret (melt_priv, + &md.melted_coin.transfer_priv[i], + &trans_sec[i]); + md.fresh_coins[i] = GNUNET_new_array (fresh_pks_len, + struct TALER_PlanchetSecretsP); + rce[i].new_coins = GNUNET_new_array (fresh_pks_len, + struct TALER_RefreshCoinData); + for (unsigned int j=0;j<fresh_pks_len;j++) + { + struct TALER_PlanchetSecretsP *fc = &md.fresh_coins[i][j]; + struct TALER_RefreshCoinData *rcd = &rce[i].new_coins[j]; + struct TALER_PlanchetDetail pd; + + TALER_planchet_setup_refresh (&trans_sec[i], + j, + fc); + if (GNUNET_OK != + TALER_planchet_prepare (&md.fresh_pks[j], + fc, + &pd)) + { + GNUNET_break_op (0); + free_melt_data (&md); + return NULL; + } + rcd->dk = &md.fresh_pks[j]; + rcd->coin_ev = pd.coin_ev; + rcd->coin_ev_size = pd.coin_ev_size; + } + } + + /* Compute refresh commitment */ + TALER_refresh_get_commitment (&md.rc, + TALER_CNC_KAPPA, + fresh_pks_len, + rce, + &coin_pub, + melt_amount); + /* finally, serialize everything */ + buf = serialize_melt_data (&md, + res_size); + for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++) + { + for (unsigned int j = 0; j < fresh_pks_len; j++) + GNUNET_free_non_null (rce[i].new_coins[j].coin_ev); + GNUNET_free_non_null (rce[i].new_coins); + } + free_melt_data (&md); + return buf; +} + + +/* ********************* /refresh/melt ***************************** */ + + +/** + * @brief A /refresh/melt Handle + */ +struct TALER_EXCHANGE_RefreshMeltHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with refresh melt failure results. + */ + TALER_EXCHANGE_RefreshMeltCallback 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; +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param rmh melt handle + * @param json json reply with the signature + * @param[out] exchange_pub public key of the exchange used for the signature + * @param[out] noreveal_index set to the noreveal index selected by the exchange + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_refresh_melt_signature_ok (struct TALER_EXCHANGE_RefreshMeltHandle *rmh, + const json_t *json, + struct TALER_ExchangePublicKeyP *exchange_pub, + uint32_t *noreveal_index) +{ + 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", noreveal_index), + GNUNET_JSON_spec_end() + }; + struct TALER_RefreshMeltConfirmationPS confirm; + + 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 = TALER_EXCHANGE_get_keys (rmh->exchange); + 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 <= *noreveal_index) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* verify signature by exchange */ + confirm.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT); + confirm.purpose.size = htonl (sizeof (struct TALER_RefreshMeltConfirmationPS)); + confirm.rc = rmh->md->rc; + confirm.noreveal_index = htonl (*noreveal_index); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT, + &confirm.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Verify that the signatures on the "403 FORBIDDEN" response from the + * exchange demonstrating customer double-spending are valid. + * + * @param rmh melt handle + * @param json json reply with the signature(s) and transaction history + * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not + */ +static int +verify_refresh_melt_signature_forbidden (struct TALER_EXCHANGE_RefreshMeltHandle *rmh, + const json_t *json) +{ + json_t *history; + struct TALER_Amount original_value; + struct TALER_Amount melt_value_with_fee; + struct TALER_Amount total; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("history", &history), + GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub), + TALER_JSON_spec_amount ("original_value", &original_value), + TALER_JSON_spec_amount ("requested_value", &melt_value_with_fee), + GNUNET_JSON_spec_end() + }; + const struct MeltedCoin *mc; + + /* parse JSON reply */ + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* Find out which coin was deemed problematic by the exchange */ + mc = &rmh->md->melted_coin; + + /* check basic coin properties */ + if (0 != TALER_amount_cmp (&original_value, + &mc->original_value)) + { + /* We disagree on the value of the coin */ + GNUNET_break_op (0); + json_decref (history); + return GNUNET_SYSERR; + } + if (0 != TALER_amount_cmp (&melt_value_with_fee, + &mc->melt_amount_with_fee)) + { + /* We disagree on the value of the coin */ + GNUNET_break_op (0); + json_decref (history); + return GNUNET_SYSERR; + } + + /* verify coin history */ + history = json_object_get (json, + "history"); + if (GNUNET_OK != + TALER_EXCHANGE_verify_coin_history (original_value.currency, + &coin_pub, + history, + &total)) + { + GNUNET_break_op (0); + json_decref (history); + return GNUNET_SYSERR; + } + json_decref (history); + + /* check if melt operation was really too expensive given history */ + if (GNUNET_OK != + TALER_amount_add (&total, + &total, + &melt_value_with_fee)) + { + /* clearly not OK if our transaction would have caused + the overflow... */ + return GNUNET_OK; + } + + if (0 >= TALER_amount_cmp (&total, + &original_value)) + { + /* transaction should have still fit */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + + /* everything OK, valid proof of double-spending was provided */ + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /refresh/melt request. + * + * @param cls the `struct TALER_EXCHANGE_RefreshMeltHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_refresh_melt_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_RefreshMeltHandle *rmh = cls; + uint32_t noreveal_index = TALER_CNC_KAPPA; /* invalid value */ + struct TALER_ExchangePublicKeyP exchange_pub; + const json_t *j = response; + + rmh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + verify_refresh_melt_signature_ok (rmh, + j, + &exchange_pub, + &noreveal_index)) + { + GNUNET_break_op (0); + response_code = 0; + } + if (NULL != rmh->melt_cb) + { + rmh->melt_cb (rmh->melt_cb_cls, + response_code, + TALER_JSON_get_error_code (j), + noreveal_index, + (0 == response_code) ? NULL : &exchange_pub, + j); + rmh->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 */ + break; + case MHD_HTTP_FORBIDDEN: + /* Double spending; check signatures on transaction history */ + if (GNUNET_OK != + verify_refresh_melt_signature_forbidden (rmh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_UNAUTHORIZED: + /* 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 */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != rmh->melt_cb) + rmh->melt_cb (rmh->melt_cb_cls, + response_code, + TALER_JSON_get_error_code (j), + UINT32_MAX, + NULL, + j); + TALER_EXCHANGE_refresh_melt_cancel (rmh); +} + + +/** + * Submit a melt request to the exchange and get the exchange's + * response. + * + * This API is typically used by a wallet. Note that to ensure that + * no money is lost in case of hardware failures, the provided + * argument should have been constructed using + * #TALER_EXCHANGE_refresh_prepare and committed to persistent storage + * prior to calling this function. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param refresh_data_length size of the @a refresh_data (returned + * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare()) + * @param refresh_data the refresh data as returned from + #TALER_EXCHANGE_refresh_prepare()) + * @param melt_cb the callback to call with the result + * @param melt_cb_cls closure for @a melt_cb + * @return a handle for this request; NULL if the argument was invalid. + * In this case, neither callback will be called. + */ +struct TALER_EXCHANGE_RefreshMeltHandle * +TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange, + size_t refresh_data_length, + const char *refresh_data, + TALER_EXCHANGE_RefreshMeltCallback melt_cb, + void *melt_cb_cls) +{ + json_t *melt_obj; + struct TALER_EXCHANGE_RefreshMeltHandle *rmh; + CURL *eh; + struct GNUNET_CURL_Context *ctx; + struct MeltData *md; + struct TALER_CoinSpendSignatureP confirm_sig; + struct TALER_RefreshMeltCoinAffirmationPS melt; + + GNUNET_assert (GNUNET_YES == + TEAH_handle_is_ready (exchange)); + md = deserialize_melt_data (refresh_data, + refresh_data_length); + if (NULL == md) + { + GNUNET_break (0); + return NULL; + } + + melt.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); + melt.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS)); + melt.rc = md->rc; + TALER_amount_hton (&melt.amount_with_fee, + &md->melted_coin.melt_amount_with_fee); + TALER_amount_hton (&melt.melt_fee, + &md->melted_coin.fee_melt); + GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv, + &melt.coin_pub.eddsa_pub); + GNUNET_CRYPTO_eddsa_sign (&md->melted_coin.coin_priv.eddsa_priv, + &melt.purpose, + &confirm_sig.eddsa_signature); + melt_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}", + "coin_pub", + GNUNET_JSON_from_data_auto (&melt.coin_pub), + "denom_pub", + GNUNET_JSON_from_rsa_public_key (md->melted_coin.pub_key.rsa_public_key), + "denom_sig", + GNUNET_JSON_from_rsa_signature (md->melted_coin.sig.rsa_signature), + "confirm_sig", + GNUNET_JSON_from_data_auto (&confirm_sig), + "value_with_fee", + TALER_JSON_from_amount (&md->melted_coin.melt_amount_with_fee), + "rc", + GNUNET_JSON_from_data_auto (&melt.rc)); + if (NULL == melt_obj) + { + GNUNET_break (0); + free_melt_data (md); + return NULL; + } + + /* and now we can at last begin the actual request handling */ + rmh = GNUNET_new (struct TALER_EXCHANGE_RefreshMeltHandle); + rmh->exchange = exchange; + rmh->melt_cb = melt_cb; + rmh->melt_cb_cls = melt_cb_cls; + rmh->md = md; + rmh->url = TEAH_path_to_url (exchange, + "/refresh/melt"); + eh = TEL_curl_easy_get (rmh->url); + GNUNET_assert (NULL != (rmh->json_enc = + json_dumps (melt_obj, + JSON_COMPACT))); + json_decref (melt_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + rmh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (rmh->json_enc))); + ctx = TEAH_handle_to_context (exchange); + rmh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_refresh_melt_finished, + rmh); + return rmh; +} + + +/** + * Cancel a refresh execute request. This function cannot be used + * on a request handle if either callback was already invoked. + * + * @param rmh the refresh melt handle + */ +void +TALER_EXCHANGE_refresh_melt_cancel (struct TALER_EXCHANGE_RefreshMeltHandle *rmh) +{ + if (NULL != rmh->job) + { + GNUNET_CURL_job_cancel (rmh->job); + rmh->job = NULL; + } + free_melt_data (rmh->md); /* does not free 'md' itself */ + GNUNET_free (rmh->md); + GNUNET_free (rmh->url); + GNUNET_free (rmh->json_enc); + GNUNET_free (rmh); +} + + +/* ********************* /refresh/reveal ***************************** */ + + +/** + * @brief A /refresh/reveal Handle + */ +struct TALER_EXCHANGE_RefreshRevealHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_RefreshRevealCallback 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 /refresh/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] coin_privs array of length `num_fresh_coins`, initialized to contain private keys + * @param[out] sigs array of length `num_fresh_coins`, initialized to cointain RSA signatures + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +refresh_reveal_ok (struct TALER_EXCHANGE_RefreshRevealHandle *rrh, + const json_t *json, + struct TALER_CoinSpendPrivateKeyP *coin_privs, + struct TALER_DenominationSignature *sigs) +{ + json_t *jsona; + struct GNUNET_JSON_Specification outer_spec[] = { + GNUNET_JSON_spec_json ("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 (! json_is_array (jsona)) + { + /* We expected an array of coins */ + GNUNET_break_op (0); + GNUNET_JSON_parse_free (outer_spec); + 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); + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_SYSERR; + } + for (unsigned int i=0;i<rrh->md->num_fresh_coins;i++) + { + const struct TALER_PlanchetSecretsP *fc; + struct TALER_DenominationPublicKey *pk; + json_t *jsonai; + struct GNUNET_CRYPTO_RsaSignature *blind_sig; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_HashCode coin_hash; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_rsa_signature ("ev_sig", &blind_sig), + GNUNET_JSON_spec_end() + }; + struct TALER_FreshCoin coin; + + fc = &rrh->md->fresh_coins[rrh->noreveal_index][i]; + pk = &rrh->md->fresh_pks[i]; + jsonai = json_array_get (jsona, i); + GNUNET_assert (NULL != jsonai); + + if (GNUNET_OK != + GNUNET_JSON_parse (jsonai, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_SYSERR; + } + + /* needed to verify the signature, and we didn't store it earlier, + hence recomputing it here... */ + GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), + &coin_hash); + if (GNUNET_OK != + TALER_planchet_to_coin (pk, + blind_sig, + fc, + &coin_hash, + &coin)) + { + GNUNET_break_op (0); + GNUNET_CRYPTO_rsa_signature_free (blind_sig); + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_rsa_signature_free (blind_sig); + coin_privs[i] = coin.coin_priv; + sigs[i] = coin.sig; + } + GNUNET_JSON_parse_free (outer_spec); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /refresh/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_RefreshRevealHandle *rrh = cls; + const json_t *j = response; + + rrh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + struct TALER_CoinSpendPrivateKeyP coin_privs[rrh->md->num_fresh_coins]; + struct TALER_DenominationSignature sigs[rrh->md->num_fresh_coins]; + int ret; + + memset (sigs, 0, sizeof (sigs)); + ret = refresh_reveal_ok (rrh, + j, + coin_privs, + sigs); + if (GNUNET_OK != ret) + { + response_code = 0; + } + else + { + rrh->reveal_cb (rrh->reveal_cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + rrh->md->num_fresh_coins, + coin_privs, + sigs, + j); + rrh->reveal_cb = NULL; + } + for (unsigned int i=0;i<rrh->md->num_fresh_coins;i++) + if (NULL != sigs[i].rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature); + } + 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_CONFLICT: + /* Nothing really to verify, exchange says our reveal is inconsitent + with our commitment, so either side is buggy; we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != rrh->reveal_cb) + rrh->reveal_cb (rrh->reveal_cb_cls, + response_code, + TALER_JSON_get_error_code (j), + 0, + NULL, + NULL, + j); + TALER_EXCHANGE_refresh_reveal_cancel (rrh); +} + + +/** + * Submit a /refresh/reval request to the exchange and get the exchange's + * response. + * + * This API is typically used by a wallet. Note that to ensure that + * no money is lost in case of hardware failures, the provided + * arguments should have been committed to persistent storage + * prior to calling this function. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param refresh_data_length size of the @a refresh_data (returned + * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare()) + * @param refresh_data the refresh data as returned from + #TALER_EXCHANGE_refresh_prepare()) + * @param noreveal_index response from the exchange to the + * #TALER_EXCHANGE_refresh_melt() invocation + * @param reveal_cb the callback to call with the final result of the + * refresh operation + * @param reveal_cb_cls closure for the above callback + * @return a handle for this request; NULL if the argument was invalid. + * In this case, neither callback will be called. + */ +struct TALER_EXCHANGE_RefreshRevealHandle * +TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange, + size_t refresh_data_length, + const char *refresh_data, + uint32_t noreveal_index, + TALER_EXCHANGE_RefreshRevealCallback reveal_cb, + void *reveal_cb_cls) +{ + struct TALER_EXCHANGE_RefreshRevealHandle *rrh; + json_t *transfer_privs; + json_t *new_denoms_h; + json_t *coin_evs; + json_t *reveal_obj; + CURL *eh; + struct GNUNET_CURL_Context *ctx; + struct MeltData *md; + struct TALER_TransferPublicKeyP transfer_pub; + + GNUNET_assert (GNUNET_YES == + TEAH_handle_is_ready (exchange)); + md = deserialize_melt_data (refresh_data, + refresh_data_length); + if (NULL == md) + { + GNUNET_break (0); + return NULL; + } + 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; + } + + /* now transfer_pub */ + GNUNET_CRYPTO_ecdhe_key_get_public (&md->melted_coin.transfer_priv[noreveal_index].ecdhe_priv, + &transfer_pub.ecdhe_pub); + + /* now new_denoms */ + GNUNET_assert (NULL != (new_denoms_h = json_array ())); + GNUNET_assert (NULL != (coin_evs = json_array ())); + for (unsigned int i=0;i<md->num_fresh_coins;i++) + { + struct GNUNET_HashCode denom_hash; + struct TALER_PlanchetDetail pd; + + GNUNET_CRYPTO_rsa_public_key_hash (md->fresh_pks[i].rsa_public_key, + &denom_hash); + GNUNET_assert (0 == + json_array_append_new (new_denoms_h, + GNUNET_JSON_from_data_auto (&denom_hash))); + + if (GNUNET_OK != + TALER_planchet_prepare (&md->fresh_pks[i], + &md->fresh_coins[noreveal_index][i], + &pd)) + { + /* This should have been noticed during the preparation stage. */ + GNUNET_break (0); + json_decref (new_denoms_h); + json_decref (coin_evs); + return NULL; + } + GNUNET_assert (0 == + json_array_append_new (coin_evs, + GNUNET_JSON_from_data (pd.coin_ev, + pd.coin_ev_size))); + GNUNET_free (pd.coin_ev); + } + + /* 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->melted_coin.transfer_priv[j]))); + } + + /* build main JSON request */ + reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o}", + "rc", + GNUNET_JSON_from_data_auto (&md->rc), + "transfer_pub", + GNUNET_JSON_from_data_auto (&transfer_pub), + "transfer_privs", + transfer_privs, + "new_denoms_h", + new_denoms_h, + "coin_evs", + coin_evs); + if (NULL == reveal_obj) + { + GNUNET_break (0); + return NULL; + } + + /* finally, we can actually issue the request */ + rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshRevealHandle); + rrh->exchange = exchange; + rrh->noreveal_index = noreveal_index; + rrh->reveal_cb = reveal_cb; + rrh->reveal_cb_cls = reveal_cb_cls; + rrh->md = md; + rrh->url = TEAH_path_to_url (rrh->exchange, + "/refresh/reveal"); + + eh = TEL_curl_easy_get (rrh->url); + GNUNET_assert (NULL != (rrh->json_enc = + json_dumps (reveal_obj, + JSON_COMPACT))); + json_decref (reveal_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + rrh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (rrh->json_enc))); + ctx = TEAH_handle_to_context (rrh->exchange); + rrh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_refresh_reveal_finished, + rrh); + return rrh; +} + + +/** + * Cancel a refresh reveal request. This function cannot be used + * on a request handle if the callback was already invoked. + * + * @param rrh the refresh reval handle + */ +void +TALER_EXCHANGE_refresh_reveal_cancel (struct TALER_EXCHANGE_RefreshRevealHandle *rrh) +{ + if (NULL != rrh->job) + { + GNUNET_CURL_job_cancel (rrh->job); + rrh->job = NULL; + } + GNUNET_free (rrh->url); + GNUNET_free (rrh->json_enc); + free_melt_data (rrh->md); /* does not free 'md' itself */ + GNUNET_free (rrh->md); + GNUNET_free (rrh); +} + + +/* end of exchange_api_refresh.c */ diff --git a/src/lib/exchange_api_refresh_link.c b/src/lib/exchange_api_refresh_link.c @@ -0,0 +1,444 @@ +/* + This file is part of TALER + Copyright (C) 2015, 2016 GNUnet e.V. + + 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 exchange-lib/exchange_api_refresh_link.c + * @brief Implementation of the /refresh/link request of the exchange's HTTP API + * @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 /refresh/link Handle + */ +struct TALER_EXCHANGE_RefreshLinkHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_RefreshLinkCallback link_cb; + + /** + * Closure for @e cb. + */ + void *link_cb_cls; + + /** + * Private key of the coin, required to decode link information. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + +}; + + +/** + * Parse the provided linkage data from the "200 OK" response + * for one of the coins. + * + * @param rlh refresh link handle + * @param json json reply with the data for one coin + * @param coin_num number of the coin to decode + * @param trans_pub our transfer public key + * @param[out] coin_priv where to return private coin key + * @param[out] sig where to return private coin signature + * @param[out] pub where to return the public key for the coin + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +parse_refresh_link_coin (const struct TALER_EXCHANGE_RefreshLinkHandle *rlh, + const json_t *json, + unsigned int coin_num, + const struct TALER_TransferPublicKeyP *trans_pub, + struct TALER_CoinSpendPrivateKeyP *coin_priv, + struct TALER_DenominationSignature *sig, + struct TALER_DenominationPublicKey *pub) +{ + struct GNUNET_CRYPTO_RsaSignature *bsig; + struct GNUNET_CRYPTO_RsaPublicKey *rpub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_rsa_public_key ("denom_pub", &rpub), + GNUNET_JSON_spec_rsa_signature ("ev_sig", &bsig), + GNUNET_JSON_spec_end() + }; + struct TALER_TransferSecretP secret; + struct TALER_PlanchetSecretsP fc; + + /* parse reply */ + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + TALER_link_recover_transfer_secret (trans_pub, + &rlh->coin_priv, + &secret); + TALER_planchet_setup_refresh (&secret, + coin_num, + &fc); + + /* extract coin and signature */ + *coin_priv = fc.coin_priv; + sig->rsa_signature + = GNUNET_CRYPTO_rsa_unblind (bsig, + &fc.blinding_key.bks, + rpub); + /* clean up */ + pub->rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (rpub); + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Parse the provided linkage data from the "200 OK" response + * for one of the coins. + * + * @param[in,out] rlh refresh link 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 int +parse_refresh_link_ok (struct TALER_EXCHANGE_RefreshLinkHandle *rlh, + const json_t *json) +{ + unsigned int session; + unsigned int num_coins; + int ret; + + if (! json_is_array (json)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + num_coins = 0; + /* Theoretically, a coin may have been melted repeatedly + into different sessions; so the response is an array + which contains information by melting session. That + array contains another array. However, our API returns + a single 1d array, so we flatten the 2d array that is + returned into a single array. Note that usually a coin + is melted at most once, and so we'll only run this + loop once for 'session=0' in most cases. + + num_coins tracks the size of the 1d array we return, + whilst 'i' and 'session' track the 2d array. */ + for (session=0;session<json_array_size (json); session++) + { + json_t *jsona; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("new_coins", &jsona), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (json, + session), + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (! json_is_array (jsona)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + + /* count all coins over all sessions */ + num_coins += json_array_size (jsona); + GNUNET_JSON_parse_free (spec); + } + /* Now that we know how big the 1d array is, allocate + and fill it. */ + { + unsigned int off_coin; /* index into 1d array */ + unsigned int i; + struct TALER_CoinSpendPrivateKeyP coin_privs[num_coins]; + struct TALER_DenominationSignature sigs[num_coins]; + struct TALER_DenominationPublicKey pubs[num_coins]; + + memset (sigs, 0, sizeof (sigs)); + memset (pubs, 0, sizeof (pubs)); + off_coin = 0; + for (session=0;session<json_array_size (json); session++) + { + json_t *jsona; + struct TALER_TransferPublicKeyP trans_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("new_coins", + &jsona), + GNUNET_JSON_spec_fixed_auto ("transfer_pub", + &trans_pub), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (json, + session), + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (! json_is_array (jsona)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + + /* decode all coins */ + for (i=0;i<json_array_size (jsona);i++) + { + GNUNET_assert (i + off_coin < num_coins); + if (GNUNET_OK != + parse_refresh_link_coin (rlh, + json_array_get (jsona, + i), + i, + &trans_pub, + &coin_privs[i+off_coin], + &sigs[i+off_coin], + &pubs[i+off_coin])) + { + GNUNET_break_op (0); + break; + } + } + /* check if we really got all, then invoke callback */ + off_coin += i; + if (i != json_array_size (jsona)) + { + GNUNET_break_op (0); + ret = GNUNET_SYSERR; + GNUNET_JSON_parse_free (spec); + break; + } + GNUNET_JSON_parse_free (spec); + } /* end of for (session) */ + + if (off_coin == num_coins) + { + rlh->link_cb (rlh->link_cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + num_coins, + coin_privs, + sigs, + pubs, + json); + rlh->link_cb = NULL; + ret = GNUNET_OK; + } + else + { + GNUNET_break_op (0); + ret = GNUNET_SYSERR; + } + + /* clean up */ + GNUNET_assert (off_coin <= num_coins); + for (i=0;i<off_coin;i++) + { + if (NULL != sigs[i].rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature); + if (NULL != pubs[i].rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (pubs[i].rsa_public_key); + } + } + return ret; +} + + +/** + * Function called when we're done processing the + * HTTP /refresh/link request. + * + * @param cls the `struct TALER_EXCHANGE_RefreshLinkHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_refresh_link_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_RefreshLinkHandle *rlh = cls; + const json_t *j = response; + + rlh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + parse_refresh_link_ok (rlh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* 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: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != rlh->link_cb) + rlh->link_cb (rlh->link_cb_cls, + response_code, + TALER_JSON_get_error_code (j), + 0, + NULL, + NULL, + NULL, + j); + TALER_EXCHANGE_refresh_link_cancel (rlh); +} + + +/** + * Submit a link request to the exchange and get the exchange's response. + * + * This API is typically not used by anyone, it is more a threat + * against those trying to receive a funds transfer by abusing the + * /refresh protocol. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param coin_priv private key to request link data for + * @param link_cb the callback to call with the useful result of the + * refresh operation the @a coin_priv was involved in (if any) + * @param link_cb_cls closure for @a link_cb + * @return a handle for this request + */ +struct TALER_EXCHANGE_RefreshLinkHandle * +TALER_EXCHANGE_refresh_link (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + TALER_EXCHANGE_RefreshLinkCallback link_cb, + void *link_cb_cls) +{ + struct TALER_EXCHANGE_RefreshLinkHandle *rlh; + CURL *eh; + struct GNUNET_CURL_Context *ctx; + struct TALER_CoinSpendPublicKeyP coin_pub; + char *pub_str; + char *arg_str; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &coin_pub.eddsa_pub); + pub_str = GNUNET_STRINGS_data_to_string_alloc (&coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP)); + GNUNET_asprintf (&arg_str, + "/refresh/link?coin_pub=%s", + pub_str); + GNUNET_free (pub_str); + + rlh = GNUNET_new (struct TALER_EXCHANGE_RefreshLinkHandle); + rlh->exchange = exchange; + rlh->link_cb = link_cb; + rlh->link_cb_cls = link_cb_cls; + rlh->coin_priv = *coin_priv; + rlh->url = TEAH_path_to_url (exchange, arg_str); + GNUNET_free (arg_str); + + + eh = TEL_curl_easy_get (rlh->url); + ctx = TEAH_handle_to_context (exchange); + rlh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_refresh_link_finished, + rlh); + return rlh; +} + + +/** + * Cancel a refresh link request. This function cannot be used + * on a request handle if the callback was already invoked. + * + * @param rlh the refresh link handle + */ +void +TALER_EXCHANGE_refresh_link_cancel (struct TALER_EXCHANGE_RefreshLinkHandle *rlh) +{ + if (NULL != rlh->job) + { + GNUNET_CURL_job_cancel (rlh->job); + rlh->job = NULL; + } + GNUNET_free (rlh->url); + GNUNET_free (rlh); +} + + +/* end of exchange_api_refresh_link.c */ diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c @@ -0,0 +1,416 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + 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 exchange-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 connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_RefundResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Information the exchange should sign in response. + */ + struct TALER_RefundConfirmationPS depconf; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param rh refund handle + * @param json json reply with the signature + * @param[out] exchange_pub set to the exchange's public key + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_refund_signature_ok (const struct TALER_EXCHANGE_RefundHandle *rh, + 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 ("sig", &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("pub", exchange_pub), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = TALER_EXCHANGE_get_keys (rh->exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND, + &rh->depconf.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub->eddsa_pub)) + { + 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; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangePublicKeyP *ep = NULL; + const json_t *j = response; + + rh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + verify_refund_signature_ok (rh, + j, + &exchange_pub)) + { + GNUNET_break_op (0); + response_code = 0; + } + else + { + ep = &exchange_pub; + } + 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: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_GONE: + /* Kind of normal: the money was already sent to the merchant + (it was too late for the refund). */ + break; + case MHD_HTTP_PRECONDITION_FAILED: + /* Client request was inconsistent; might be a currency missmatch + problem. */ + break; + case MHD_HTTP_CONFLICT: + /* Two refund requests were made about the same deposit, but + carrying different refund transaction ids. */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + rh->cb (rh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + ep, + j); + TALER_EXCHANGE_refund_cancel (rh); +} + + +/** + * Submit a refund request to the exchange and get the exchange's + * response. This API is used by a merchant. Note that + * while we return the response verbatim to the caller for further + * processing, we do already verify that the response is well-formed + * (i.e. that signatures included in the response are all valid). If + * the exchange's reply is not well-formed, we return an HTTP status code + * of zero to @a cb. + * + * The @a exchange must be ready to operate (i.e. have + * finished processing the /keys reply). If this check fails, we do + * NOT initiate the transaction with the exchange and instead return NULL. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param amount the amount to be refunded; must be larger than the refund fee + * (as that fee is still being subtracted), and smaller than the amount + * (with deposit fee) of the original deposit contribution of this coin + * @param refund_fee fee applicable to this coin for the refund + * @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded + * @param coin_pub coin’s public key of the coin from the original deposit operation + * @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation); + * this is needed as we may first do a partial refund and later a full refund. If both + * refunds are also over the same amount, we need the @a rtransaction_id to make the disjoint + * refund requests different (as requests are idempotent and otherwise the 2nd refund might not work). + * @param merchant_priv the private key of the merchant, used to generate signature for refund request + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + * signatures fail to verify). In this case, the callback is not called. + */ +struct TALER_EXCHANGE_RefundHandle * +TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_Amount *amount, + const struct TALER_Amount *refund_fee, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + uint64_t rtransaction_id, + const struct TALER_MerchantPrivateKeyP *merchant_priv, + TALER_EXCHANGE_RefundResultCallback cb, + void *cb_cls) +{ + struct TALER_RefundRequestPS rr; + struct TALER_MerchantSignatureP merchant_sig; + + GNUNET_assert (GNUNET_YES == + TEAH_handle_is_ready (exchange)); + rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND); + rr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS)); + rr.h_contract_terms = *h_contract_terms; + rr.coin_pub = *coin_pub; + GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, + &rr.merchant.eddsa_pub); + rr.rtransaction_id = GNUNET_htonll (rtransaction_id); + TALER_amount_hton (&rr.refund_amount, + amount); + TALER_amount_hton (&rr.refund_fee, + refund_fee); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv, + &rr.purpose, + &merchant_sig.eddsa_sig)); + return TALER_EXCHANGE_refund2 (exchange, + amount, + refund_fee, + h_contract_terms, + coin_pub, + rtransaction_id, + &rr.merchant, + &merchant_sig, + cb, + cb_cls); +} + + +/** + * Submit a refund request to the exchange and get the exchange's + * response. This API is used by a merchant. Note that + * while we return the response verbatim to the caller for further + * processing, we do already verify that the response is well-formed + * (i.e. that signatures included in the response are all valid). If + * the exchange's reply is not well-formed, we return an HTTP status code + * of zero to @a cb. + * + * The @a exchange must be ready to operate (i.e. have + * finished processing the /keys reply). If this check fails, we do + * NOT initiate the transaction with the exchange and instead return NULL. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param amount the amount to be refunded; must be larger than the refund fee + * (as that fee is still being subtracted), and smaller than the amount + * (with deposit fee) of the original deposit contribution of this coin + * @param refund_fee fee applicable to this coin for the refund + * @param h_contract_terms hash of the contact of the merchant with the customer that is being refunded + * @param coin_pub coin’s public key of the coin from the original deposit operation + * @param rtransaction_id transaction id for the transaction between merchant and customer (of refunding operation); + * this is needed as we may first do a partial refund and later a full refund. If both + * refunds are also over the same amount, we need the @a rtransaction_id to make the disjoint + * refund requests different (as requests are idempotent and otherwise the 2nd refund might not work). + * @param merchant_pub public key of the merchant + * @param merchant_sig signature affirming the refund from the merchant + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + * signatures fail to verify). In this case, the callback is not called. + */ +struct TALER_EXCHANGE_RefundHandle * +TALER_EXCHANGE_refund2 (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_Amount *amount, + const struct TALER_Amount *refund_fee, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + uint64_t rtransaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_MerchantSignatureP *merchant_sig, + TALER_EXCHANGE_RefundResultCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_RefundHandle *rh; + struct GNUNET_CURL_Context *ctx; + json_t *refund_obj; + CURL *eh; + +refund_obj = json_pack ("{s:o, s:o," /* amount/fee */ + " s:o, s:o," /* h_contract_terms, coin_pub */ + " s:I," /* rtransaction id */ + " s:o, s:o}", /* merchant_pub, merchant_sig */ + "refund_amount", TALER_JSON_from_amount (amount), + "refund_fee", TALER_JSON_from_amount (refund_fee), + "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms), + "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), + "rtransaction_id", (json_int_t) rtransaction_id, + "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub), + "merchant_sig", GNUNET_JSON_from_data_auto (merchant_sig) + ); + if (NULL == refund_obj) + { + GNUNET_break (0); + return NULL; + } + + rh = GNUNET_new (struct TALER_EXCHANGE_RefundHandle); + rh->exchange = exchange; + rh->cb = cb; + rh->cb_cls = cb_cls; + rh->url = TEAH_path_to_url (exchange, "/refund"); + rh->depconf.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS)); + rh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND); + rh->depconf.h_contract_terms = *h_contract_terms; + rh->depconf.coin_pub = *coin_pub; + rh->depconf.merchant = *merchant_pub; + rh->depconf.rtransaction_id = GNUNET_htonll (rtransaction_id); + TALER_amount_hton (&rh->depconf.refund_amount, + amount); + TALER_amount_hton (&rh->depconf.refund_fee, + refund_fee); + + eh = TEL_curl_easy_get (rh->url); + GNUNET_assert (NULL != (rh->json_enc = + json_dumps (refund_obj, + JSON_COMPACT))); + json_decref (refund_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for refund: `%s'\n", + rh->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + rh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (rh->json_enc))); + ctx = TEAH_handle_to_context (exchange); + rh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_refund_finished, + rh); + return rh; +} + + +/** + * Cancel a refund permission request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param refund the refund permission request handle + */ +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); + GNUNET_free (refund->json_enc); + GNUNET_free (refund); +} + + +/* end of exchange_api_refund.c */ diff --git a/src/lib/exchange_api_reserve.c b/src/lib/exchange_api_reserve.c @@ -0,0 +1,1203 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + 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 exchange-lib/exchange_api_reserve.c + * @brief Implementation of the /reserve requests 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_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/* ********************** /reserve/status ********************** */ + +/** + * @brief A Withdraw Status Handle + */ +struct TALER_EXCHANGE_ReserveStatusHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ReserveStatusResultCallback cb; + + /** + * Public key of the reserve we are querying. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * Parse history given in JSON format and return it in binary + * format. + * + * @param exchange connection to the exchange we can use + * @param history JSON array with the history + * @param reserve_pub public key of the reserve to inspect + * @param currency currency we expect the balance to be in + * @param[out] balance final balance + * @param history_length number of entries in @a history + * @param[out] rhistory array of length @a history_length, set to the + * parsed history entries + * @return #GNUNET_OK if history was valid and @a rhistory and @a balance + * were set, + * #GNUNET_SYSERR if there was a protocol violation in @a history + */ +static int +parse_reserve_history (struct TALER_EXCHANGE_Handle *exchange, + const json_t *history, + const struct TALER_ReservePublicKeyP *reserve_pub, + const char *currency, + struct TALER_Amount *balance, + unsigned int history_length, + struct TALER_EXCHANGE_ReserveHistory *rhistory) +{ + struct GNUNET_HashCode uuid[history_length]; + unsigned int uuid_off; + struct TALER_Amount total_in; + struct TALER_Amount total_out; + size_t off; + + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_in)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_out)); + uuid_off = 0; + for (off=0;off<history_length;off++) + { + json_t *transaction; + struct TALER_Amount amount; + const char *type; + struct GNUNET_JSON_Specification hist_spec[] = { + GNUNET_JSON_spec_string ("type", &type), + TALER_JSON_spec_amount ("amount", + &amount), + /* 'wire' and 'signature' are optional depending on 'type'! */ + GNUNET_JSON_spec_end() + }; + + transaction = json_array_get (history, + off); + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + hist_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rhistory[off].amount = amount; + + if (0 == strcasecmp (type, + "DEPOSIT")) + { + const char *wire_url; + void *wire_reference; + size_t wire_reference_size; + struct GNUNET_TIME_Absolute timestamp; + + struct GNUNET_JSON_Specification withdraw_spec[] = { + GNUNET_JSON_spec_varsize ("wire_reference", + &wire_reference, + &wire_reference_size), + GNUNET_JSON_spec_absolute_time ("timestamp", + &timestamp), + GNUNET_JSON_spec_string ("sender_account_url", + &wire_url), + GNUNET_JSON_spec_end() + }; + + rhistory[off].type = TALER_EXCHANGE_RTT_DEPOSIT; + if (GNUNET_OK != + TALER_amount_add (&total_in, + &total_in, + &amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + withdraw_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rhistory[off].details.in_details.sender_url = GNUNET_strdup (wire_url); + rhistory[off].details.in_details.wire_reference = wire_reference; + rhistory[off].details.in_details.wire_reference_size = wire_reference_size; + rhistory[off].details.in_details.timestamp = timestamp; + /* end type==DEPOSIT */ + } + else if (0 == strcasecmp (type, + "WITHDRAW")) + { + struct TALER_ReserveSignatureP sig; + struct TALER_WithdrawRequestPS withdraw_purpose; + struct GNUNET_JSON_Specification withdraw_spec[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &sig), + TALER_JSON_spec_amount_nbo ("withdraw_fee", + &withdraw_purpose.withdraw_fee), + GNUNET_JSON_spec_fixed_auto ("h_denom_pub", + &withdraw_purpose.h_denomination_pub), + GNUNET_JSON_spec_fixed_auto ("h_coin_envelope", + &withdraw_purpose.h_coin_envelope), + GNUNET_JSON_spec_end() + }; + unsigned int i; + + rhistory[off].type = TALER_EXCHANGE_RTT_WITHDRAWAL; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + withdraw_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + withdraw_purpose.purpose.size + = htonl (sizeof (withdraw_purpose)); + withdraw_purpose.purpose.purpose + = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); + withdraw_purpose.reserve_pub = *reserve_pub; + TALER_amount_hton (&withdraw_purpose.amount_with_fee, + &amount); + /* Check that the signature is a valid withdraw request */ + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, + &withdraw_purpose.purpose, + &sig.eddsa_signature, + &reserve_pub->eddsa_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + /* TODO: check that withdraw fee matches expectations! */ + rhistory[off].details.out_authorization_sig + = json_object_get (transaction, + "signature"); + /* Check check that the same withdraw transaction + isn't listed twice by the exchange. We use the + "uuid" array to remember the hashes of all + purposes, and compare the hashes to find + duplicates. */ + GNUNET_CRYPTO_hash (&withdraw_purpose, + ntohl (withdraw_purpose.purpose.size), + &uuid[uuid_off]); + for (i=0;i<uuid_off;i++) + { + if (0 == memcmp (&uuid[uuid_off], + &uuid[i], + sizeof (struct GNUNET_HashCode))) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + } + uuid_off++; + + if (GNUNET_OK != + TALER_amount_add (&total_out, + &total_out, + &amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + /* end type==WITHDRAW */ + } + else if (0 == strcasecmp (type, + "PAYBACK")) + { + struct TALER_PaybackConfirmationPS pc; + struct GNUNET_TIME_Absolute timestamp; + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification payback_spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &pc.coin_pub), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rhistory[off].details.payback_details.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rhistory[off].details.payback_details.exchange_pub), + GNUNET_JSON_spec_absolute_time_nbo ("timestamp", + &pc.timestamp), + GNUNET_JSON_spec_end() + }; + + rhistory[off].type = TALER_EXCHANGE_RTT_PAYBACK; + rhistory[off].amount = amount; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + payback_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rhistory[off].details.payback_details.coin_pub = pc.coin_pub; + TALER_amount_hton (&pc.payback_amount, + &amount); + pc.purpose.size = htonl (sizeof (pc)); + pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK); + pc.reserve_pub = *reserve_pub; + timestamp = GNUNET_TIME_absolute_ntoh (pc.timestamp); + rhistory[off].details.payback_details.timestamp = timestamp; + + key_state = TALER_EXCHANGE_get_keys (exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + &rhistory[off].details.payback_details.exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK, + &pc.purpose, + &rhistory[off].details.payback_details.exchange_sig.eddsa_signature, + &rhistory[off].details.payback_details.exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&total_in, + &total_in, + &rhistory[off].amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* end type==PAYBACK */ + } + else if (0 == strcasecmp (type, + "CLOSING")) + { + const struct TALER_EXCHANGE_Keys *key_state; + struct TALER_ReserveCloseConfirmationPS rcc; + struct GNUNET_TIME_Absolute timestamp; + struct GNUNET_JSON_Specification closing_spec[] = { + GNUNET_JSON_spec_string ("receiver_account_details", + &rhistory[off].details.close_details.receiver_account_details), + GNUNET_JSON_spec_fixed_auto ("wtid", + &rhistory[off].details.close_details.wtid), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rhistory[off].details.close_details.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rhistory[off].details.close_details.exchange_pub), + TALER_JSON_spec_amount_nbo ("closing_fee", + &rcc.closing_fee), + GNUNET_JSON_spec_absolute_time_nbo ("timestamp", + &rcc.timestamp), + GNUNET_JSON_spec_end() + }; + + rhistory[off].type = TALER_EXCHANGE_RTT_CLOSE; + rhistory[off].amount = amount; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + closing_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + TALER_amount_hton (&rcc.closing_amount, + &amount); + GNUNET_CRYPTO_hash (rhistory[off].details.close_details.receiver_account_details, + strlen (rhistory[off].details.close_details.receiver_account_details) + 1, + &rcc.h_wire); + rcc.wtid = rhistory[off].details.close_details.wtid; + rcc.purpose.size = htonl (sizeof (rcc)); + rcc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED); + rcc.reserve_pub = *reserve_pub; + timestamp = GNUNET_TIME_absolute_ntoh (rcc.timestamp); + rhistory[off].details.close_details.timestamp = timestamp; + + key_state = TALER_EXCHANGE_get_keys (exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + &rhistory[off].details.close_details.exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED, + &rcc.purpose, + &rhistory[off].details.close_details.exchange_sig.eddsa_signature, + &rhistory[off].details.close_details.exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (&total_out, + &total_out, + &rhistory[off].amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* end type==CLOSING */ + } + else + { + /* unexpected 'type', protocol incompatibility, complain! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + + /* check balance = total_in - total_out < withdraw-amount */ + if (GNUNET_SYSERR == + TALER_amount_subtract (balance, + &total_in, + &total_out)) + { + /* total_in < total_out, why did the exchange ever allow this!? */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Free memory (potentially) allocated by #parse_reserve_history(). + * + * @param rhistory result to free + * @param len number of entries in @a rhistory + */ +static void +free_rhistory (struct TALER_EXCHANGE_ReserveHistory *rhistory, + unsigned int len) +{ + for (unsigned int i=0;i<len;i++) + { + switch (rhistory[i].type) + { + case TALER_EXCHANGE_RTT_DEPOSIT: + GNUNET_free_non_null (rhistory[i].details.in_details.wire_reference); + GNUNET_free_non_null (rhistory[i].details.in_details.sender_url); + break; + case TALER_EXCHANGE_RTT_WITHDRAWAL: + break; + case TALER_EXCHANGE_RTT_PAYBACK: + break; + case TALER_EXCHANGE_RTT_CLOSE: + // should we free "receiver_account_details" ? + break; + } + } +} + + +/** + * Function called when we're done processing the + * HTTP /reserve/status request. + * + * @param cls the `struct TALER_EXCHANGE_ReserveStatusHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserve_status_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ReserveStatusHandle *rsh = cls; + const json_t *j = response; + + rsh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + /* TODO: move into separate function... */ + json_t *history; + unsigned int len; + struct TALER_Amount balance; + struct TALER_Amount balance_from_history; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("balance", &balance), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + history = json_object_get (j, + "history"); + if (NULL == history) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + len = json_array_size (history); + { + struct TALER_EXCHANGE_ReserveHistory rhistory[len]; + + memset (rhistory, 0, sizeof (rhistory)); + if (GNUNET_OK != + parse_reserve_history (rsh->exchange, + history, + &rsh->reserve_pub, + balance.currency, + &balance_from_history, + len, + rhistory)) + { + GNUNET_break_op (0); + response_code = 0; + } + if ( (0 != response_code) && + (0 != + TALER_amount_cmp (&balance_from_history, + &balance)) ) + { + /* exchange cannot add up balances!? */ + GNUNET_break_op (0); + response_code = 0; + } + if (0 != response_code) + { + rsh->cb (rsh->cb_cls, + response_code, + TALER_EC_NONE, + j, + &balance, + len, + rhistory); + rsh->cb = NULL; + } + free_rhistory (rhistory, + len); + } + } + 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_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != rsh->cb) + { + rsh->cb (rsh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + j, + NULL, + 0, NULL); + rsh->cb = NULL; + } + TALER_EXCHANGE_reserve_status_cancel (rsh); +} + + +/** + * Submit a request to obtain the transaction history of a reserve + * from the exchange. Note that while we return the full response to the + * caller for further processing, we do already verify that the + * response is well-formed (i.e. that signatures included in the + * response are all valid and add up to the balance). If the exchange's + * reply is not well-formed, we return an HTTP status code of zero to + * @a cb. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param reserve_pub public key of the reserve to inspect + * @param cb the callback to call when a reply for this request is available + * @param cb_cls closure for the above callback + * @return a handle for this request; NULL if the inputs are invalid (i.e. + * signatures fail to verify). In this case, the callback is not called. + */ +struct TALER_EXCHANGE_ReserveStatusHandle * +TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_ReservePublicKeyP *reserve_pub, + TALER_EXCHANGE_ReserveStatusResultCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ReserveStatusHandle *rsh; + struct GNUNET_CURL_Context *ctx; + CURL *eh; + char *pub_str; + char *arg_str; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + pub_str = GNUNET_STRINGS_data_to_string_alloc (reserve_pub, + sizeof (struct TALER_ReservePublicKeyP)); + GNUNET_asprintf (&arg_str, + "/reserve/status?reserve_pub=%s", + pub_str); + GNUNET_free (pub_str); + rsh = GNUNET_new (struct TALER_EXCHANGE_ReserveStatusHandle); + rsh->exchange = exchange; + rsh->cb = cb; + rsh->cb_cls = cb_cls; + rsh->reserve_pub = *reserve_pub; + rsh->url = TEAH_path_to_url (exchange, + arg_str); + GNUNET_free (arg_str); + + eh = TEL_curl_easy_get (rsh->url); + ctx = TEAH_handle_to_context (exchange); + rsh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_NO, + &handle_reserve_status_finished, + rsh); + return rsh; +} + + +/** + * Cancel a reserve status request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param rsh the reserve status request handle + */ +void +TALER_EXCHANGE_reserve_status_cancel (struct TALER_EXCHANGE_ReserveStatusHandle *rsh) +{ + if (NULL != rsh->job) + { + GNUNET_CURL_job_cancel (rsh->job); + rsh->job = NULL; + } + GNUNET_free (rsh->url); + GNUNET_free (rsh); +} + + +/* ********************** /reserve/withdraw ********************** */ + +/** + * @brief A Withdraw Sign Handle + */ +struct TALER_EXCHANGE_ReserveWithdrawHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ReserveWithdrawResultCallback cb; + + /** + * Secrets of the planchet. + */ + struct TALER_PlanchetSecretsP ps; + + /** + * Denomination key we are withdrawing. + */ + const struct TALER_EXCHANGE_DenomPublicKey *pk; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Hash of the public key of the coin we are signing. + */ + struct GNUNET_HashCode c_hash; + + /** + * Public key of the reserve we are withdrawing from. + */ + struct TALER_ReservePublicKeyP reserve_pub; + +}; + + +/** + * We got a 200 OK response for the /reserve/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 wsh operation handle + * @param json reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +reserve_withdraw_ok (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh, + const json_t *json) +{ + struct GNUNET_CRYPTO_RsaSignature *blind_sig; + struct TALER_FreshCoin fc; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_rsa_signature ("ev_sig", + &blind_sig), + 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_planchet_to_coin (&wsh->pk->key, + blind_sig, + &wsh->ps, + &wsh->c_hash, + &fc)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + GNUNET_JSON_parse_free (spec); + + /* signature is valid, return it to the application */ + wsh->cb (wsh->cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + &fc.sig, + json); + /* make sure callback isn't called again after return */ + wsh->cb = NULL; + GNUNET_CRYPTO_rsa_signature_free (fc.sig.rsa_signature); + return GNUNET_OK; +} + + +/** + * We got a 403 FORBIDDEN response for the /reserve/withdraw operation. + * Check the signatures on the withdraw transactions in the provided + * history and that the balances add up. We don't do anything directly + * with the information, as the JSON will be returned to the application. + * However, our job is ensuring that the exchange followed the protocol, and + * this in particular means checking all of the signatures in the history. + * + * @param wsh operation handle + * @param json reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +reserve_withdraw_payment_required (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh, + const json_t *json) +{ + struct TALER_Amount balance; + struct TALER_Amount balance_from_history; + struct TALER_Amount requested_amount; + json_t *history; + size_t len; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("balance", &balance), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + history = json_object_get (json, + "history"); + if (NULL == history) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* go over transaction history and compute + total incoming and outgoing amounts */ + len = json_array_size (history); + { + struct TALER_EXCHANGE_ReserveHistory *rhistory; + + /* Use heap allocation as "len" may be very big and thus this may + not fit on the stack. Use "GNUNET_malloc_large" as a malicious + exchange may theoretically try to crash us by giving a history + that does not fit into our memory. */ + rhistory = GNUNET_malloc_large (sizeof (struct TALER_EXCHANGE_ReserveHistory) * len); + if (NULL == rhistory) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + parse_reserve_history (wsh->exchange, + history, + &wsh->reserve_pub, + balance.currency, + &balance_from_history, + len, + rhistory)) + { + GNUNET_break_op (0); + free_rhistory (rhistory, + len); + return GNUNET_SYSERR; + } + free_rhistory (rhistory, + len); + } + + if (0 != + TALER_amount_cmp (&balance_from_history, + &balance)) + { + /* exchange cannot add up balances!? */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* Compute how much we expected to charge to the reserve */ + if (GNUNET_OK != + TALER_amount_add (&requested_amount, + &wsh->pk->value, + &wsh->pk->fee_withdraw)) + { + /* Overflow here? Very strange, our CPU must be fried... */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* Check that funds were really insufficient */ + if (0 >= TALER_amount_cmp (&requested_amount, + &balance)) + { + /* Requested amount is smaller or equal to reported balance, + so this should not have failed. */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserve/withdraw request. + * + * @param cls the `struct TALER_EXCHANGE_ReserveWithdrawHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserve_withdraw_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh = cls; + const json_t *j = response; + + wsh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + reserve_withdraw_ok (wsh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + /* The exchange says that the reserve has insufficient funds; + check the signatures in the history... */ + if (GNUNET_OK != + reserve_withdraw_payment_required (wsh, + j)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_UNAUTHORIZED: + GNUNET_break (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 */ + 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. */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != wsh->cb) + { + wsh->cb (wsh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + NULL, + j); + wsh->cb = NULL; + } + TALER_EXCHANGE_reserve_withdraw_cancel (wsh); +} + + +/** + * Helper function for #TALER_EXCHANGE_reserve_withdraw2() and + * #TALER_EXCHANGE_reserve_withdraw(). + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param pk kind of coin to create + * @param reserve_sig signature from the reserve authorizing the withdrawal + * @param reserve_pub public key of the reserve to withdraw from + * @param ps secrets of the planchet + * caller must have committed this value to disk before the call (with @a pk) + * @param pd planchet details matching @a ps + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for @a res_cb + * @return NULL + * if the inputs are invalid (i.e. denomination key not with this exchange). + * In this case, the callback is not called. + */ +struct TALER_EXCHANGE_ReserveWithdrawHandle * +reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_EXCHANGE_DenomPublicKey *pk, + const struct TALER_ReserveSignatureP *reserve_sig, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_PlanchetSecretsP *ps, + const struct TALER_PlanchetDetail *pd, + TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; + struct GNUNET_CURL_Context *ctx; + json_t *withdraw_obj; + CURL *eh; + + wsh = GNUNET_new (struct TALER_EXCHANGE_ReserveWithdrawHandle); + wsh->exchange = exchange; + wsh->cb = res_cb; + wsh->cb_cls = res_cb_cls; + wsh->pk = pk; + wsh->reserve_pub = *reserve_pub; + wsh->c_hash = pd->c_hash; + withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub and coin_ev */ + " s:o, s:o}",/* reserve_pub and reserve_sig */ + "denom_pub", GNUNET_JSON_from_rsa_public_key (pk->key.rsa_public_key), + "coin_ev", GNUNET_JSON_from_data (pd->coin_ev, + pd->coin_ev_size), + "reserve_pub", GNUNET_JSON_from_data_auto (reserve_pub), + "reserve_sig", GNUNET_JSON_from_data_auto (reserve_sig)); + if (NULL == withdraw_obj) + { + GNUNET_break (0); + return NULL; + } + + wsh->ps = *ps; + wsh->url = TEAH_path_to_url (exchange, "/reserve/withdraw"); + + eh = TEL_curl_easy_get (wsh->url); + GNUNET_assert (NULL != (wsh->json_enc = + json_dumps (withdraw_obj, + JSON_COMPACT))); + json_decref (withdraw_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + wsh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (wsh->json_enc))); + ctx = TEAH_handle_to_context (exchange); + wsh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_reserve_withdraw_finished, + wsh); + return wsh; +} + + +/** + * Withdraw a coin from the exchange using a /reserve/withdraw request. Note + * that to ensure that no money is lost in case of hardware failures, + * the caller must have committed (most of) the arguments to disk + * before calling, and be ready to repeat the request with the same + * arguments in case of failures. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param pk kind of coin to create + * @param reserve_priv private key of the reserve to withdraw from + * @param ps secrets of the planchet + * caller must have committed this value to disk before the call (with @a pk) + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for the above callback + * @return handle for the operation on success, NULL on error, i.e. + * if the inputs are invalid (i.e. denomination key not with this exchange). + * In this case, the callback is not called. + */ +struct TALER_EXCHANGE_ReserveWithdrawHandle * +TALER_EXCHANGE_reserve_withdraw (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_EXCHANGE_DenomPublicKey *pk, + const struct TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_PlanchetSecretsP *ps, + TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb, + void *res_cb_cls) +{ + struct TALER_Amount amount_with_fee; + struct TALER_ReserveSignatureP reserve_sig; + struct TALER_WithdrawRequestPS req; + struct TALER_PlanchetDetail pd; + struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; + + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &req.reserve_pub.eddsa_pub); + req.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS)); + req.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); + if (GNUNET_OK != + TALER_amount_add (&amount_with_fee, + &pk->fee_withdraw, + &pk->value)) + { + /* exchange gave us denomination keys that overflow like this!? */ + GNUNET_break_op (0); + return NULL; + } + TALER_amount_hton (&req.amount_with_fee, + &amount_with_fee); + TALER_amount_hton (&req.withdraw_fee, + &pk->fee_withdraw); + if (GNUNET_OK != + TALER_planchet_prepare (&pk->key, + ps, + &pd)) + { + GNUNET_break_op (0); + return NULL; + } + req.h_denomination_pub = pd.denom_pub_hash; + GNUNET_CRYPTO_hash (pd.coin_ev, + pd.coin_ev_size, + &req.h_coin_envelope); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, + &req.purpose, + &reserve_sig.eddsa_signature)); + wsh = reserve_withdraw_internal (exchange, + pk, + &reserve_sig, + &req.reserve_pub, + ps, + &pd, + res_cb, + res_cb_cls); + GNUNET_free (pd.coin_ev); + return wsh; +} + + +/** + * Withdraw a coin from the exchange using a /reserve/withdraw + * request. This API is typically used by a wallet to withdraw a tip + * where the reserve's signature was created by the merchant already. + * + * Note that to ensure that no money is lost in case of hardware + * failures, the caller must have committed (most of) the arguments to + * disk before calling, and be ready to repeat the request with the + * same arguments in case of failures. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param pk kind of coin to create + * @param reserve_sig signature from the reserve authorizing the withdrawal + * @param reserve_pub public key of the reserve to withdraw from + * @param ps secrets of the planchet + * caller must have committed this value to disk before the call (with @a pk) + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for @a res_cb + * @return NULL + * if the inputs are invalid (i.e. denomination key not with this exchange). + * In this case, the callback is not called. + */ +struct TALER_EXCHANGE_ReserveWithdrawHandle * +TALER_EXCHANGE_reserve_withdraw2 (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_EXCHANGE_DenomPublicKey *pk, + const struct TALER_ReserveSignatureP *reserve_sig, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_PlanchetSecretsP *ps, + TALER_EXCHANGE_ReserveWithdrawResultCallback res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; + struct TALER_PlanchetDetail pd; + + if (GNUNET_OK != + TALER_planchet_prepare (&pk->key, + ps, + &pd)) + { + GNUNET_break_op (0); + return NULL; + } + wsh = reserve_withdraw_internal (exchange, + pk, + reserve_sig, + reserve_pub, + ps, + &pd, + res_cb, + res_cb_cls); + GNUNET_free (pd.coin_ev); + return wsh; +} + + +/** + * Cancel a withdraw status request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param sign the withdraw sign request handle + */ +void +TALER_EXCHANGE_reserve_withdraw_cancel (struct TALER_EXCHANGE_ReserveWithdrawHandle *sign) +{ + if (NULL != sign->job) + { + GNUNET_CURL_job_cancel (sign->job); + sign->job = NULL; + } + GNUNET_free (sign->url); + GNUNET_free (sign->json_enc); + GNUNET_free (sign); +} + + +/* end of exchange_api_reserve.c */ diff --git a/src/lib/exchange_api_track_transaction.c b/src/lib/exchange_api_track_transaction.c @@ -0,0 +1,367 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + 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 exchange-lib/exchange_api_track_transaction.c + * @brief Implementation of the /track/transaction 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 Deposit Wtid Handle + */ +struct TALER_EXCHANGE_TrackTransactionHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_TrackTransactionCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Information the exchange should sign in response. + * (with pre-filled fields from the request). + */ + struct TALER_ConfirmWirePS depconf; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param dwh deposit wtid handle + * @param json json reply with the signature + * @param[out] exchange_pub set to the exchange's public key + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_deposit_wtid_signature_ok (const struct TALER_EXCHANGE_TrackTransactionHandle *dwh, + 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_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = TALER_EXCHANGE_get_keys (dwh->exchange); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE, + &dwh->depconf.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /track/transaction request. + * + * @param cls the `struct TALER_EXCHANGE_TrackTransactionHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_deposit_wtid_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_TrackTransactionHandle *dwh = cls; + const struct TALER_WireTransferIdentifierRawP *wtid = NULL; + struct GNUNET_TIME_Absolute execution_time = GNUNET_TIME_UNIT_FOREVER_ABS; + const struct TALER_Amount *coin_contribution = NULL; + struct TALER_Amount coin_contribution_s; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangePublicKeyP *ep = NULL; + const json_t *j = response; + + dwh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("wtid", &dwh->depconf.wtid), + GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time), + TALER_JSON_spec_amount ("coin_contribution", &coin_contribution_s), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + wtid = &dwh->depconf.wtid; + dwh->depconf.execution_time = GNUNET_TIME_absolute_hton (execution_time); + TALER_amount_hton (&dwh->depconf.coin_contribution, + &coin_contribution_s); + coin_contribution = &coin_contribution_s; + if (GNUNET_OK != + verify_deposit_wtid_signature_ok (dwh, + j, + &exchange_pub)) + { + GNUNET_break_op (0); + response_code = 0; + } + else + { + ep = &exchange_pub; + } + } + break; + case MHD_HTTP_ACCEPTED: + { + /* Transaction known, but not executed yet */ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_absolute_time ("execution_time", &execution_time), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Exchange does not know about transaction; + we should pass the reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + dwh->cb (dwh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + ep, + j, + wtid, + execution_time, + coin_contribution); + TALER_EXCHANGE_track_transaction_cancel (dwh); +} + + +/** + * Obtain wire transfer details about an existing deposit operation. + * + * @param exchange the exchange to query + * @param merchant_priv the merchant's private key + * @param h_wire hash of merchant's wire transfer details + * @param h_contract_terms hash of the proposal data from the contract + * between merchant and customer + * @param coin_pub public key of the coin + * @param cb function to call with the result + * @param cb_cls closure for @a cb + * @return handle to abort request + */ +struct TALER_EXCHANGE_TrackTransactionHandle * +TALER_EXCHANGE_track_transaction (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_MerchantPrivateKeyP *merchant_priv, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + TALER_EXCHANGE_TrackTransactionCallback cb, + void *cb_cls) +{ + struct TALER_DepositTrackPS dtp; + struct TALER_MerchantSignatureP merchant_sig; + struct TALER_EXCHANGE_TrackTransactionHandle *dwh; + struct GNUNET_CURL_Context *ctx; + json_t *deposit_wtid_obj; + CURL *eh; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + dtp.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION); + dtp.purpose.size = htonl (sizeof (dtp)); + dtp.h_contract_terms = *h_contract_terms; + dtp.h_wire = *h_wire; + GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, + &dtp.merchant.eddsa_pub); + + dtp.coin_pub = *coin_pub; + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv, + &dtp.purpose, + &merchant_sig.eddsa_sig)); + deposit_wtid_obj = json_pack ("{s:o, s:o," /* H_wire, h_contract_terms */ + " s:o," /* coin_pub */ + " s:o, s:o}", /* merchant_pub, merchant_sig */ + "H_wire", GNUNET_JSON_from_data_auto (h_wire), + "h_contract_terms", GNUNET_JSON_from_data_auto (h_contract_terms), + "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), + "merchant_pub", GNUNET_JSON_from_data_auto (&dtp.merchant), + "merchant_sig", GNUNET_JSON_from_data_auto (&merchant_sig)); + if (NULL == deposit_wtid_obj) + { + GNUNET_break (0); + return NULL; + } + + dwh = GNUNET_new (struct TALER_EXCHANGE_TrackTransactionHandle); + dwh->exchange = exchange; + dwh->cb = cb; + dwh->cb_cls = cb_cls; + dwh->url = TEAH_path_to_url (exchange, "/track/transaction"); + dwh->depconf.purpose.size = htonl (sizeof (struct TALER_ConfirmWirePS)); + dwh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE); + dwh->depconf.h_wire = *h_wire; + dwh->depconf.h_contract_terms = *h_contract_terms; + dwh->depconf.coin_pub = *coin_pub; + + eh = TEL_curl_easy_get (dwh->url); + GNUNET_assert (NULL != (dwh->json_enc = + json_dumps (deposit_wtid_obj, + JSON_COMPACT))); + json_decref (deposit_wtid_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + dwh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (dwh->json_enc))); + ctx = TEAH_handle_to_context (exchange); + dwh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_deposit_wtid_finished, + dwh); + return dwh; +} + + +/** + * Cancel deposit wtid request. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param dwh the wire deposits request handle + */ +void +TALER_EXCHANGE_track_transaction_cancel (struct TALER_EXCHANGE_TrackTransactionHandle *dwh) +{ + if (NULL != dwh->job) + { + GNUNET_CURL_job_cancel (dwh->job); + dwh->job = NULL; + } + GNUNET_free (dwh->url); + GNUNET_free (dwh->json_enc); + GNUNET_free (dwh); +} + + +/* end of exchange_api_deposit_wtid.c */ diff --git a/src/lib/exchange_api_track_transfer.c b/src/lib/exchange_api_track_transfer.c @@ -0,0 +1,388 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016 GNUnet e.V. + + 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 exchange-lib/exchange_api_track_transfer.c + * @brief Implementation of the /track/transfer 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_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 /track/transfer Handle + */ +struct TALER_EXCHANGE_TrackTransferHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_TrackTransferCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * We got a #MHD_HTTP_OK response for the /track/transfer request. + * Check that the response is well-formed and if it is, call the + * callback. If not, return an error code. + * + * This code is very similar to + * merchant_api_track_transfer.c::check_track_transfer_response_ok. + * Any changes should likely be reflected there as well. + * + * @param wdh handle to the operation + * @param json response we got + * @return #GNUNET_OK if we are done and all is well, + * #GNUNET_SYSERR if the response was bogus + */ +static int +check_track_transfer_response_ok (struct TALER_EXCHANGE_TrackTransferHandle *wdh, + const json_t *json) +{ + json_t *details_j; + struct GNUNET_HashCode h_wire; + struct GNUNET_TIME_Absolute exec_time; + struct TALER_Amount total_amount; + struct TALER_Amount total_expected; + struct TALER_Amount wire_fee; + struct TALER_MerchantPublicKeyP merchant_pub; + unsigned int num_details; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("total", &total_amount), + TALER_JSON_spec_amount ("wire_fee", &wire_fee), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub), + GNUNET_JSON_spec_fixed_auto ("H_wire", &h_wire), + GNUNET_JSON_spec_absolute_time ("execution_time", &exec_time), + GNUNET_JSON_spec_json ("deposits", &details_j), + 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_amount_get_zero (total_amount.currency, + &total_expected)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + num_details = json_array_size (details_j); + { + struct TALER_TrackTransferDetails details[num_details]; + unsigned int i; + struct GNUNET_HashContext *hash_context; + struct TALER_WireDepositDetailP dd; + struct TALER_WireDepositDataPS wdp; + + hash_context = GNUNET_CRYPTO_hash_context_start (); + for (i=0;i<num_details;i++) + { + struct TALER_TrackTransferDetails *detail = &details[i]; + struct json_t *detail_j = json_array_get (details_j, i); + struct GNUNET_JSON_Specification spec_detail[] = { + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &detail->h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub), + TALER_JSON_spec_amount ("deposit_value", &detail->coin_value), + TALER_JSON_spec_amount ("deposit_fee", &detail->coin_fee), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (detail_j, + spec_detail, + NULL, NULL)) + { + GNUNET_break_op (0); + GNUNET_CRYPTO_hash_context_abort (hash_context); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + /* build up big hash for signature checking later */ + dd.h_contract_terms = detail->h_contract_terms; + dd.execution_time = GNUNET_TIME_absolute_hton (exec_time); + dd.coin_pub = detail->coin_pub; + TALER_amount_hton (&dd.deposit_value, + &detail->coin_value); + TALER_amount_hton (&dd.deposit_fee, + &detail->coin_fee); + if ( (GNUNET_OK != + TALER_amount_add (&total_expected, + &total_expected, + &detail->coin_value)) || + (GNUNET_OK != + TALER_amount_subtract (&total_expected, + &total_expected, + &detail->coin_fee)) ) + { + GNUNET_break_op (0); + GNUNET_CRYPTO_hash_context_abort (hash_context); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_hash_context_read (hash_context, + &dd, + sizeof (struct TALER_WireDepositDetailP)); + } + /* Check signature */ + wdp.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT); + wdp.purpose.size = htonl (sizeof (struct TALER_WireDepositDataPS)); + TALER_amount_hton (&wdp.total, + &total_amount); + TALER_amount_hton (&wdp.wire_fee, + &wire_fee); + wdp.merchant_pub = merchant_pub; + wdp.h_wire = h_wire; + GNUNET_CRYPTO_hash_context_finish (hash_context, + &wdp.h_details); + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (TALER_EXCHANGE_get_keys (wdh->exchange), + &exchange_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify + (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT, + &wdp.purpose, + &exchange_sig.eddsa_signature, + &exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + TALER_amount_subtract (&total_expected, + &total_expected, + &wire_fee)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + if (0 != + TALER_amount_cmp (&total_expected, + &total_amount)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + wdh->cb (wdh->cb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + &exchange_pub, + json, + &h_wire, + exec_time, + &total_amount, + &wire_fee, + num_details, + details); + } + GNUNET_JSON_parse_free (spec); + TALER_EXCHANGE_track_transfer_cancel (wdh); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /track/transfer request. + * + * @param cls the `struct TALER_EXCHANGE_TrackTransferHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_track_transfer_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_TrackTransferHandle *wdh = cls; + const json_t *j = response; + + wdh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + if (GNUNET_OK == + check_track_transfer_response_ok (wdh, + j)) + return; + GNUNET_break_op (0); + response_code = 0; + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Exchange does not know about transaction; + we should pass the reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + wdh->cb (wdh->cb_cls, + response_code, + TALER_JSON_get_error_code (j), + NULL, + j, + NULL, + GNUNET_TIME_UNIT_ZERO_ABS, + NULL, + NULL, + 0, NULL); + TALER_EXCHANGE_track_transfer_cancel (wdh); +} + + +/** + * Query the exchange about which transactions were combined + * to create a wire transfer. + * + * @param exchange exchange to query + * @param wtid raw wire transfer identifier to get information about + * @param cb callback to call + * @param cb_cls closure for @a cb + * @return handle to cancel operation + */ +struct TALER_EXCHANGE_TrackTransferHandle * +TALER_EXCHANGE_track_transfer (struct TALER_EXCHANGE_Handle *exchange, + const struct TALER_WireTransferIdentifierRawP *wtid, + TALER_EXCHANGE_TrackTransferCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_TrackTransferHandle *wdh; + struct GNUNET_CURL_Context *ctx; + char *buf; + char *path; + CURL *eh; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + + wdh = GNUNET_new (struct TALER_EXCHANGE_TrackTransferHandle); + wdh->exchange = exchange; + wdh->cb = cb; + wdh->cb_cls = cb_cls; + + buf = GNUNET_STRINGS_data_to_string_alloc (wtid, + sizeof (struct TALER_WireTransferIdentifierRawP)); + GNUNET_asprintf (&path, + "/track/transfer?wtid=%s", + buf); + wdh->url = TEAH_path_to_url (wdh->exchange, + path); + GNUNET_free (buf); + GNUNET_free (path); + + eh = TEL_curl_easy_get (wdh->url); + ctx = TEAH_handle_to_context (exchange); + wdh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_track_transfer_finished, + wdh); + return wdh; +} + + +/** + * Cancel wire deposits request. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param wdh the wire deposits request handle + */ +void +TALER_EXCHANGE_track_transfer_cancel (struct TALER_EXCHANGE_TrackTransferHandle *wdh) +{ + if (NULL != wdh->job) + { + GNUNET_CURL_job_cancel (wdh->job); + wdh->job = NULL; + } + GNUNET_free (wdh->url); + GNUNET_free (wdh); +} + + +/* end of exchange_api_wire_deposits.c */ diff --git a/src/lib/exchange_api_wire.c b/src/lib/exchange_api_wire.c @@ -0,0 +1,442 @@ +/* + 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 exchange-lib/exchange_api_wire.c + * @brief Implementation of the /wire 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_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "taler_wire_lib.h" +#include "taler_signatures.h" +#include "taler_wire_plugin.h" +#include "exchange_api_handle.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Wire Handle + */ +struct TALER_EXCHANGE_WireHandle +{ + + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_WireResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * List of wire fees by method. + */ +struct FeeMap +{ + /** + * Next entry in list. + */ + struct FeeMap *next; + + /** + * Wire method this fee structure is for. + */ + char *method; + + /** + * Array of wire fees, also linked list, but allocated + * only once. + */ + struct TALER_EXCHANGE_WireAggregateFees *fee_list; +}; + + +/** + * Frees @a fm. + * + * @param fm memory to release + */ +static void +free_fees (struct FeeMap *fm) +{ + while (NULL != fm) + { + struct FeeMap *fe = fm->next; + + GNUNET_free (fm->fee_list); + GNUNET_free (fm->method); + GNUNET_free (fm); + fm = fe; + } +} + + +/** + * Parse wire @a fees and return map. + * + * @param fees json AggregateTransferFee to parse + * @return NULL on error + */ +static struct FeeMap * +parse_fees (json_t *fees) +{ + struct FeeMap *fm = NULL; + const char *key; + json_t *fee_array; + + json_object_foreach (fees, key, fee_array) { + struct FeeMap *fe = GNUNET_new (struct FeeMap); + int len; + unsigned int idx; + json_t *fee; + + if (0 == (len = json_array_size (fee_array))) + { + GNUNET_break_op (0); + GNUNET_free (fe); + continue; /* skip */ + } + fe->method = GNUNET_strdup (key); + fe->next = fm; + fe->fee_list = GNUNET_new_array (len, + struct TALER_EXCHANGE_WireAggregateFees); + fm = fe; + json_array_foreach (fee_array, idx, fee) + { + struct TALER_EXCHANGE_WireAggregateFees *wa = &fe->fee_list[idx]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("sig", + &wa->master_sig), + TALER_JSON_spec_amount ("wire_fee", + &wa->wire_fee), + TALER_JSON_spec_amount ("closing_fee", + &wa->closing_fee), + GNUNET_JSON_spec_absolute_time ("start_date", + &wa->start_date), + GNUNET_JSON_spec_absolute_time ("end_date", + &wa->end_date), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (fee, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + free_fees (fm); + return NULL; + } + if (idx + 1 < len) + wa->next = &fe->fee_list[idx + 1]; + else + wa->next = NULL; + } + } + return fm; +} + + +/** + * Find fee by @a method. + * + * @param fm map to look in + * @param method key to look for + * @return NULL if fee is not specified in @a fm + */ +static const struct TALER_EXCHANGE_WireAggregateFees * +lookup_fee (const struct FeeMap *fm, + const char *method) +{ + for (;NULL != fm; fm = fm->next) + if (0 == strcasecmp (fm->method, + method)) + return fm->fee_list; + return NULL; +} + + +/** + * Function called when we're done processing the + * HTTP /wire request. + * + * @param cls the `struct TALER_EXCHANGE_WireHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_wire_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_WireHandle *wh = cls; + enum TALER_ErrorCode ec; + const json_t *j = response; + + TALER_LOG_DEBUG ("Checking raw /wire response\n"); + wh->job = NULL; + ec = TALER_EC_NONE; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + { + json_t *accounts; + json_t *fees; + int num_accounts; + struct FeeMap *fm; + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("accounts", &accounts), + GNUNET_JSON_spec_json ("fees", &fees), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + /* bogus reply */ + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_SERVER_JSON_INVALID; + break; + } + if (0 == (num_accounts = json_array_size (accounts))) + { + /* bogus reply */ + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + response_code = 0; + ec = TALER_EC_SERVER_JSON_INVALID; + break; + } + if (NULL == (fm = parse_fees (fees))) + { + /* bogus reply */ + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + response_code = 0; + ec = TALER_EC_SERVER_JSON_INVALID; + break; + } + + key_state = TALER_EXCHANGE_get_keys (wh->exchange); + /* parse accounts */ + { + struct TALER_EXCHANGE_WireAccount was[num_accounts]; + + for (unsigned int i=0;i<num_accounts;i++) + { + struct TALER_EXCHANGE_WireAccount *wa = &was[i]; + json_t *account; + struct GNUNET_JSON_Specification spec_account[] = { + GNUNET_JSON_spec_string ("url", &wa->url), + GNUNET_JSON_spec_fixed_auto ("master_sig", &wa->master_sig), + GNUNET_JSON_spec_end() + }; + char *method; + + account = json_array_get (accounts, + i); + if (GNUNET_OK != + TALER_JSON_exchange_wire_signature_check (account, + &key_state->master_pub)) + { + /* bogus reply */ + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_SERVER_SIGNATURE_INVALID; + break; + } + if (GNUNET_OK != + GNUNET_JSON_parse (account, + spec_account, + NULL, NULL)) + { + /* bogus reply */ + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_SERVER_JSON_INVALID; + break; + } + if (NULL == (method = TALER_WIRE_payto_get_method (wa->url))) + { + /* bogus reply */ + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_SERVER_JSON_INVALID; + break; + } + if (NULL == (wa->fees = lookup_fee (fm, + method))) + { + /* bogus reply */ + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_SERVER_JSON_INVALID; + GNUNET_free (method); + break; + } + GNUNET_free (method); + } /* end 'for all accounts */ + if ( (0 != response_code) && + (NULL != wh->cb) ) + { + wh->cb (wh->cb_cls, + response_code, + TALER_EC_NONE, + num_accounts, + was); + wh->cb = NULL; + } + } /* end of 'parse accounts */ + free_fees (fm); + GNUNET_JSON_parse_free (spec); + } /* end of MHD_HTTP_OK */ + 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_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + response_code = 0; + break; + } + if (NULL != wh->cb) + wh->cb (wh->cb_cls, + response_code, + (0 == response_code) ? ec : TALER_JSON_get_error_code (j), + 0, + NULL); + TALER_EXCHANGE_wire_cancel (wh); +} + + +/** + * Obtain information about a exchange's wire instructions. + * A exchange may provide wire instructions for creating + * a reserve. The wire instructions also indicate + * which wire formats merchants may use with the exchange. + * This API is typically used by a wallet for wiring + * funds, and possibly by a merchant to determine + * supported wire formats. + * + * Note that while we return the (main) response verbatim to the + * caller for further processing, we do already verify that the + * response is well-formed (i.e. that signatures included in the + * response are all valid). If the exchange's reply is not well-formed, + * we return an HTTP status code of zero to @a cb. + * + * @param exchange the exchange handle; the exchange must be ready to operate + * @param wire_cb the callback to call when a reply for this request is available + * @param wire_cb_cls closure for the above callback + * @return a handle for this request + */ +struct TALER_EXCHANGE_WireHandle * +TALER_EXCHANGE_wire (struct TALER_EXCHANGE_Handle *exchange, + TALER_EXCHANGE_WireResultCallback wire_cb, + void *wire_cb_cls) +{ + struct TALER_EXCHANGE_WireHandle *wh; + struct GNUNET_CURL_Context *ctx; + CURL *eh; + + if (GNUNET_YES != + TEAH_handle_is_ready (exchange)) + { + GNUNET_break (0); + return NULL; + } + wh = GNUNET_new (struct TALER_EXCHANGE_WireHandle); + wh->exchange = exchange; + wh->cb = wire_cb; + wh->cb_cls = wire_cb_cls; + wh->url = TEAH_path_to_url (exchange, "/wire"); + + eh = TEL_curl_easy_get (wh->url); + ctx = TEAH_handle_to_context (exchange); + wh->job = GNUNET_CURL_job_add (ctx, + eh, + GNUNET_YES, + &handle_wire_finished, + wh); + return wh; +} + + +/** + * Cancel a wire information request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param wh the wire information request handle + */ +void +TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh) +{ + if (NULL != wh->job) + { + GNUNET_CURL_job_cancel (wh->job); + wh->job = NULL; + } + GNUNET_free (wh->url); + GNUNET_free (wh); +} + + +/* end of exchange_api_wire.c */ diff --git a/src/auditor-lib/test_auditor_api.c b/src/lib/test_auditor_api.c diff --git a/src/auditor-lib/test_auditor_api.conf b/src/lib/test_auditor_api.conf diff --git a/src/auditor-lib/test_auditor_api_expire_reserve_now.conf b/src/lib/test_auditor_api_expire_reserve_now.conf diff --git a/src/exchange-lib/test_exchange_api.conf b/src/lib/test_exchange_api.conf diff --git a/src/exchange-lib/test_exchange_api_expire_reserve_now.conf b/src/lib/test_exchange_api_expire_reserve_now.conf diff --git a/src/auditor-lib/test_exchange_api_home/.config/taler/account-1.json b/src/lib/test_exchange_api_home/.config/taler/account-1.json diff --git a/src/auditor-lib/test_exchange_api_home/.config/taler/account-2.json b/src/lib/test_exchange_api_home/.config/taler/account-2.json diff --git a/src/auditor-lib/test_exchange_api_home/.config/taler/sepa.json b/src/lib/test_exchange_api_home/.config/taler/sepa.json diff --git a/src/auditor-lib/test_exchange_api_home/.config/taler/test.json b/src/lib/test_exchange_api_home/.config/taler/test.json diff --git a/src/auditor-lib/test_exchange_api_home/.config/taler/x-taler-bank.json b/src/lib/test_exchange_api_home/.config/taler/x-taler-bank.json diff --git a/src/auditor-lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv b/src/lib/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv diff --git a/src/exchange-lib/test_exchange_api_keys_cherry_picking.conf b/src/lib/test_exchange_api_keys_cherry_picking.conf diff --git a/src/exchange-lib/test_exchange_api_keys_cherry_picking_extended.conf b/src/lib/test_exchange_api_keys_cherry_picking_extended.conf diff --git a/src/exchange-lib/test_exchange_api_keys_cherry_picking_extended_2.conf b/src/lib/test_exchange_api_keys_cherry_picking_extended_2.conf diff --git a/src/exchange-lib/test_exchange_api_keys_cherry_picking_new.c b/src/lib/test_exchange_api_keys_cherry_picking_new.c diff --git a/src/exchange-lib/test_exchange_api_new.c b/src/lib/test_exchange_api_new.c diff --git a/src/exchange-lib/test_exchange_api_overlapping_keys_bug.c b/src/lib/test_exchange_api_overlapping_keys_bug.c diff --git a/src/exchange-lib/test_exchange_api_twisted.c b/src/lib/test_exchange_api_twisted.c diff --git a/src/exchange-lib/test_exchange_api_twisted.conf b/src/lib/test_exchange_api_twisted.conf diff --git a/src/exchange-lib/testing_api_cmd_bank_check.c b/src/lib/testing_api_cmd_bank_check.c diff --git a/src/exchange-lib/testing_api_cmd_batch.c b/src/lib/testing_api_cmd_batch.c diff --git a/src/exchange-lib/testing_api_cmd_check_keys.c b/src/lib/testing_api_cmd_check_keys.c diff --git a/src/exchange-lib/testing_api_cmd_deposit.c b/src/lib/testing_api_cmd_deposit.c diff --git a/src/exchange-lib/testing_api_cmd_exec_aggregator.c b/src/lib/testing_api_cmd_exec_aggregator.c diff --git a/src/exchange-lib/testing_api_cmd_exec_auditor-sign.c b/src/lib/testing_api_cmd_exec_auditor-sign.c diff --git a/src/exchange-lib/testing_api_cmd_exec_keyup.c b/src/lib/testing_api_cmd_exec_keyup.c diff --git a/src/exchange-lib/testing_api_cmd_exec_wirewatch.c b/src/lib/testing_api_cmd_exec_wirewatch.c diff --git a/src/exchange-lib/testing_api_cmd_fakebank_transfer.c b/src/lib/testing_api_cmd_fakebank_transfer.c diff --git a/src/exchange-lib/testing_api_cmd_payback.c b/src/lib/testing_api_cmd_payback.c diff --git a/src/exchange-lib/testing_api_cmd_refresh.c b/src/lib/testing_api_cmd_refresh.c diff --git a/src/exchange-lib/testing_api_cmd_refund.c b/src/lib/testing_api_cmd_refund.c diff --git a/src/exchange-lib/testing_api_cmd_serialize_keys.c b/src/lib/testing_api_cmd_serialize_keys.c diff --git a/src/exchange-lib/testing_api_cmd_signal.c b/src/lib/testing_api_cmd_signal.c diff --git a/src/exchange-lib/testing_api_cmd_sleep.c b/src/lib/testing_api_cmd_sleep.c diff --git a/src/exchange-lib/testing_api_cmd_status.c b/src/lib/testing_api_cmd_status.c diff --git a/src/exchange-lib/testing_api_cmd_track.c b/src/lib/testing_api_cmd_track.c diff --git a/src/exchange-lib/testing_api_cmd_wire.c b/src/lib/testing_api_cmd_wire.c diff --git a/src/exchange-lib/testing_api_cmd_withdraw.c b/src/lib/testing_api_cmd_withdraw.c diff --git a/src/exchange-lib/testing_api_helpers.c b/src/lib/testing_api_helpers.c diff --git a/src/exchange-lib/testing_api_loop.c b/src/lib/testing_api_loop.c diff --git a/src/exchange-lib/testing_api_trait_amount.c b/src/lib/testing_api_trait_amount.c diff --git a/src/exchange-lib/testing_api_trait_blinding_key.c b/src/lib/testing_api_trait_blinding_key.c diff --git a/src/exchange-lib/testing_api_trait_cmd.c b/src/lib/testing_api_trait_cmd.c diff --git a/src/exchange-lib/testing_api_trait_coin_priv.c b/src/lib/testing_api_trait_coin_priv.c diff --git a/src/exchange-lib/testing_api_trait_denom_pub.c b/src/lib/testing_api_trait_denom_pub.c diff --git a/src/exchange-lib/testing_api_trait_denom_sig.c b/src/lib/testing_api_trait_denom_sig.c diff --git a/src/exchange-lib/testing_api_trait_exchange_pub.c b/src/lib/testing_api_trait_exchange_pub.c diff --git a/src/exchange-lib/testing_api_trait_exchange_sig.c b/src/lib/testing_api_trait_exchange_sig.c diff --git a/src/exchange-lib/testing_api_trait_fresh_coin.c b/src/lib/testing_api_trait_fresh_coin.c diff --git a/src/exchange-lib/testing_api_trait_json.c b/src/lib/testing_api_trait_json.c diff --git a/src/exchange-lib/testing_api_trait_key_peer.c b/src/lib/testing_api_trait_key_peer.c diff --git a/src/exchange-lib/testing_api_trait_number.c b/src/lib/testing_api_trait_number.c diff --git a/src/exchange-lib/testing_api_trait_process.c b/src/lib/testing_api_trait_process.c diff --git a/src/exchange-lib/testing_api_trait_reserve_priv.c b/src/lib/testing_api_trait_reserve_priv.c diff --git a/src/exchange-lib/testing_api_trait_string.c b/src/lib/testing_api_trait_string.c diff --git a/src/exchange-lib/testing_api_trait_wtid.c b/src/lib/testing_api_trait_wtid.c diff --git a/src/exchange-lib/testing_api_traits.c b/src/lib/testing_api_traits.c diff --git a/src/lib/testing_auditor_api_cmd_deposit_confirmation.c b/src/lib/testing_auditor_api_cmd_deposit_confirmation.c @@ -0,0 +1,411 @@ +/* + This file is part of TALER + Copyright (C) 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 auditor-lib/testing_auditor_api_cmd_deposit_confirmation.c + * @brief command for testing /deposit_confirmation. + * @author Christian Grothoff + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_auditor_service.h" +#include "taler_testing_lib.h" +#include "taler_signatures.h" +#include "backoff.h" + + +/** + * State for a "deposit confirmation" CMD. + */ +struct DepositConfirmationState +{ + + /** + * Reference to any command that is able to provide a deposit. + */ + const char *deposit_reference; + + /** + * What is the deposited amount without the fee (i.e. the + * amount we expect in the deposit confirmation)? + */ + const char *amount_without_fee; + + /** + * Which coin of the @e deposit_reference should we confirm. + */ + unsigned int coin_index; + + /** + * DepositConfirmation handle while operation is running. + */ + struct TALER_AUDITOR_DepositConfirmationHandle *dc; + + /** + * Auditor connection. + */ + struct TALER_AUDITOR_Handle *auditor; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Should we retry on (transient) failures? + */ + int do_retry; + +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +deposit_confirmation_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #deposit_confirmation_run. + * + * @param cls a `struct DepositConfirmationState` + */ +static void +do_retry (void *cls) +{ + struct DepositConfirmationState *dcs = cls; + + dcs->retry_task = NULL; + deposit_confirmation_run (dcs, + NULL, + dcs->is); +} + + +/** + * Callback to analyze the /deposit-confirmation response, just used + * to check if the response code is acceptable. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param obj raw response from the auditor. + */ +static void +deposit_confirmation_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const json_t *obj) +{ + struct DepositConfirmationState *dcs = cls; + + dcs->dc = NULL; + if (dcs->expected_response_code != http_status) + { + if (GNUNET_YES == dcs->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying deposit confirmation failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + dcs->backoff = GNUNET_TIME_UNIT_ZERO; + else + dcs->backoff = EXCHANGE_LIB_BACKOFF (dcs->backoff); + dcs->retry_task = GNUNET_SCHEDULER_add_delayed (dcs->backoff, + &do_retry, + dcs); + return; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s in %s:%u\n", + http_status, + dcs->is->commands[dcs->is->ip].label, + __FILE__, + __LINE__); + json_dumpf (obj, stderr, 0); + TALER_TESTING_interpreter_fail (dcs->is); + return; + } + TALER_TESTING_interpreter_next (dcs->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +deposit_confirmation_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct DepositConfirmationState *dcs = cls; + const struct TALER_TESTING_Command *deposit_cmd; + struct GNUNET_HashCode h_wire; + struct GNUNET_HashCode h_contract_terms; + struct GNUNET_TIME_Absolute timestamp; + struct GNUNET_TIME_Absolute refund_deadline; + struct TALER_Amount amount_without_fee; + struct TALER_CoinSpendPublicKeyP coin_pub; + const struct GNUNET_CRYPTO_EddsaPrivateKey *merchant_priv; + struct TALER_MerchantPublicKeyP merchant_pub; + const struct TALER_ExchangePublicKeyP *exchange_pub; + const struct TALER_ExchangeSignatureP *exchange_sig; + const json_t *wire_details; + const json_t *contract_terms; + const struct TALER_CoinSpendPrivateKeyP *coin_priv; + const struct TALER_EXCHANGE_Keys *keys; + const struct TALER_EXCHANGE_SigningPublicKey *spk; + + dcs->is = is; + GNUNET_assert (NULL != dcs->deposit_reference); + deposit_cmd + = TALER_TESTING_interpreter_lookup_command (is, + dcs->deposit_reference); + if (NULL == deposit_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_exchange_pub (deposit_cmd, + dcs->coin_index, + &exchange_pub)); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_exchange_sig (deposit_cmd, + dcs->coin_index, + &exchange_sig)); + keys = TALER_EXCHANGE_get_keys (dcs->is->exchange); + GNUNET_assert (NULL != keys); + spk = TALER_EXCHANGE_get_exchange_signing_key_info (keys, + exchange_pub); + + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_contract_terms (deposit_cmd, + dcs->coin_index, + &contract_terms)); + /* Very unlikely to fail */ + GNUNET_assert (NULL != contract_terms); + TALER_JSON_hash (contract_terms, + &h_contract_terms); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_wire_details (deposit_cmd, + dcs->coin_index, + &wire_details)); + TALER_JSON_hash (wire_details, + &h_wire); + + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_coin_priv (deposit_cmd, + dcs->coin_index, + &coin_priv)); + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &coin_pub.eddsa_pub); + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_peer_key (deposit_cmd, + dcs->coin_index, + &merchant_priv)); + GNUNET_CRYPTO_eddsa_key_get_public (merchant_priv, + &merchant_pub.eddsa_pub); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (dcs->amount_without_fee, + &amount_without_fee)); + + dcs->dc = TALER_AUDITOR_deposit_confirmation + (dcs->auditor, + &h_wire, + &h_contract_terms, + timestamp, + refund_deadline, + &amount_without_fee, + &coin_pub, + &merchant_pub, + exchange_pub, + exchange_sig, + &keys->master_pub, + spk->valid_from, + spk->valid_until, + spk->valid_legal, + &spk->master_sig, + &deposit_confirmation_cb, + dcs); + + if (NULL == dcs->dc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + return; +} + + +/** + * Free the state of a "deposit_confirmation" CMD, and possibly cancel a + * pending operation thereof. + * + * @param cls closure, a `struct DepositConfirmationState` + * @param cmd the command which is being cleaned up. + */ +static void +deposit_confirmation_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct DepositConfirmationState *dcs = cls; + + if (NULL != dcs->dc) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + dcs->is->ip, + cmd->label); + TALER_AUDITOR_deposit_confirmation_cancel (dcs->dc); + dcs->dc = NULL; + } + if (NULL != dcs->retry_task) + { + GNUNET_SCHEDULER_cancel (dcs->retry_task); + dcs->retry_task = NULL; + } + GNUNET_free (dcs); +} + + +/** + * Offer internal data to other commands. + * + * @param cls closure. + * @param ret[out] set to the wanted data. + * @param trait name of the trait. + * @param index index number of the traits to be returned. + * + * @return #GNUNET_OK on success + */ +static int +deposit_confirmation_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + /* Must define this function because some callbacks + * look for certain traits on _all_ the commands. */ + return GNUNET_SYSERR; +} + + +/** + * Create a "deposit-confirmation" command. + * + * @param label command label. + * @param auditor auditor connection. + * @param deposit_reference reference to any operation that can + * provide a coin. + * @param coin_index if @a deposit_reference offers an array of + * coins, this parameter selects which one in that array. + * This value is currently ignored, as only one-coin + * deposits are implemented. + * @param amount_without_fee deposited amount without the fee + * @param expected_response_code expected HTTP response code. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_deposit_confirmation + (const char *label, + struct TALER_AUDITOR_Handle *auditor, + const char *deposit_reference, + unsigned int coin_index, + const char *amount_without_fee, + unsigned int expected_response_code) +{ + struct TALER_TESTING_Command cmd = {0}; /* need explicit zeroing..*/ + struct DepositConfirmationState *dcs; + + dcs = GNUNET_new (struct DepositConfirmationState); + dcs->auditor = auditor; + dcs->deposit_reference = deposit_reference; + dcs->coin_index = coin_index; + dcs->amount_without_fee = amount_without_fee; + dcs->expected_response_code = expected_response_code; + + cmd.cls = dcs; + cmd.label = label; + cmd.run = &deposit_confirmation_run; + cmd.cleanup = &deposit_confirmation_cleanup; + cmd.traits = &deposit_confirmation_traits; + + return cmd; +} + + +/** + * Modify a deposit confirmation command to enable retries when we get + * transient errors from the auditor. + * + * @param cmd a deposit confirmation command + * @return the command with retries enabled + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_deposit_confirmation_with_retry (struct TALER_TESTING_Command cmd) +{ + struct DepositConfirmationState *dcs; + + GNUNET_assert (&deposit_confirmation_run == cmd.run); + dcs = cmd.cls; + dcs->do_retry = GNUNET_YES; + return cmd; +} + + +/* end of testing_auditor_api_cmd_deposit_confirmation.c */ diff --git a/src/lib/testing_auditor_api_cmd_exchanges.c b/src/lib/testing_auditor_api_cmd_exchanges.c @@ -0,0 +1,296 @@ +/* + This file is part of TALER + Copyright (C) 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 auditor-lib/testing_auditor_api_cmd_exchanges.c + * @brief command for testing /exchanges. + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_auditor_service.h" +#include "taler_testing_lib.h" +#include "taler_signatures.h" +#include "backoff.h" + + +/** + * State for a "deposit confirmation" CMD. + */ +struct ExchangesState +{ + + /** + * Exchanges handle while operation is running. + */ + struct TALER_AUDITOR_ListExchangesHandle *leh; + + /** + * Auditor connection. + */ + struct TALER_AUDITOR_Handle *auditor; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Task scheduled to try later. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * How long do we wait until we retry? + */ + struct GNUNET_TIME_Relative backoff; + + /** + * Expected HTTP response code. + */ + unsigned int expected_response_code; + + /** + * Should we retry on (transient) failures? + */ + int do_retry; + +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +exchanges_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #exchanges_run. + * + * @param cls a `struct ExchangesState` + */ +static void +do_retry (void *cls) +{ + struct ExchangesState *es = cls; + + es->retry_task = NULL; + exchanges_run (es, + NULL, + es->is); +} + + +/** + * Callback to analyze the /exchanges response. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param obj raw response from the auditor. + */ +static void +exchanges_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + unsigned int num_exchanges, + const struct TALER_AUDITOR_ExchangeInfo *ei, + const json_t *raw_response) +{ + struct ExchangesState *es = cls; + + es->leh = NULL; + if (es->expected_response_code != http_status) + { + if (GNUNET_YES == es->do_retry) + { + if ( (0 == http_status) || + (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || + (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Retrying list exchanges failed with %u/%d\n", + http_status, + (int) ec); + /* on DB conflicts, do not use backoff */ + if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) + es->backoff = GNUNET_TIME_UNIT_ZERO; + else + es->backoff = EXCHANGE_LIB_BACKOFF (es->backoff); + es->retry_task = GNUNET_SCHEDULER_add_delayed (es->backoff, + &do_retry, + es); + return; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s in %s:%u\n", + http_status, + es->is->commands[es->is->ip].label, + __FILE__, + __LINE__); + json_dumpf (raw_response, stderr, 0); + TALER_TESTING_interpreter_fail (es->is); + return; + } + TALER_TESTING_interpreter_next (es->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +exchanges_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct ExchangesState *es = cls; + + es->is = is; + es->leh = TALER_AUDITOR_list_exchanges + (es->auditor, + &exchanges_cb, + es); + + if (NULL == es->leh) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + return; +} + + +/** + * Free the state of a "exchanges" CMD, and possibly cancel a + * pending operation thereof. + * + * @param cls closure, a `struct ExchangesState` + * @param cmd the command which is being cleaned up. + */ +static void +exchanges_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct ExchangesState *es = cls; + + if (NULL != es->leh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + es->is->ip, + cmd->label); + TALER_AUDITOR_list_exchanges_cancel (es->leh); + es->leh = NULL; + } + if (NULL != es->retry_task) + { + GNUNET_SCHEDULER_cancel (es->retry_task); + es->retry_task = NULL; + } + GNUNET_free (es); +} + + +/** + * Offer internal data to other commands. + * + * @param cls closure. + * @param ret[out] set to the wanted data. + * @param trait name of the trait. + * @param index index number of the traits to be returned. + * + * @return #GNUNET_OK on success + */ +static int +exchanges_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + /* Must define this function because some callbacks + * look for certain traits on _all_ the commands. */ + return GNUNET_SYSERR; +} + + +/** + * Create a "list exchanges" command. + * + * @param label command label. + * @param auditor auditor connection. + * @param expected_response_code expected HTTP response code. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exchanges + (const char *label, + struct TALER_AUDITOR_Handle *auditor, + unsigned int expected_response_code) +{ + struct TALER_TESTING_Command cmd = {0}; /* need explicit zeroing..*/ + struct ExchangesState *es; + + es = GNUNET_new (struct ExchangesState); + es->auditor = auditor; + es->expected_response_code = expected_response_code; + + cmd.cls = es; + cmd.label = label; + cmd.run = &exchanges_run; + cmd.cleanup = &exchanges_cleanup; + cmd.traits = &exchanges_traits; + + return cmd; +} + + +/** + * Modify an exchanges command to enable retries when we get + * transient errors from the auditor. + * + * @param cmd a deposit confirmation command + * @return the command with retries enabled + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exchanges_with_retry (struct TALER_TESTING_Command cmd) +{ + struct ExchangesState *es; + + GNUNET_assert (&exchanges_run == cmd.run); + es = cmd.cls; + es->do_retry = GNUNET_YES; + return cmd; +} + + +/* end of testing_auditor_api_cmd_exchanges.c */ diff --git a/src/auditor-lib/testing_auditor_api_cmd_exec_auditor.c b/src/lib/testing_auditor_api_cmd_exec_auditor.c diff --git a/src/auditor-lib/testing_auditor_api_cmd_exec_auditor_dbinit.c b/src/lib/testing_auditor_api_cmd_exec_auditor_dbinit.c diff --git a/src/auditor-lib/testing_auditor_api_cmd_exec_wire_auditor.c b/src/lib/testing_auditor_api_cmd_exec_wire_auditor.c diff --git a/src/auditor-lib/testing_auditor_api_helpers.c b/src/lib/testing_auditor_api_helpers.c