diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/auditor-lib/Makefile.am | 36 | ||||
-rw-r--r-- | src/auditor-lib/auditor_api_common.c | 328 | ||||
-rw-r--r-- | src/auditor-lib/auditor_api_deposit_confirmation.c | 365 | ||||
-rw-r--r-- | src/auditor-lib/auditor_api_handle.c | 570 | ||||
-rw-r--r-- | src/auditor-lib/auditor_api_handle.h | 71 | ||||
-rw-r--r-- | src/auditor-lib/curl_defaults.c | 74 |
6 files changed, 1444 insertions, 0 deletions
diff --git a/src/auditor-lib/Makefile.am b/src/auditor-lib/Makefile.am new file mode 100644 index 000000000..de2399961 --- /dev/null +++ b/src/auditor-lib/Makefile.am @@ -0,0 +1,36 @@ +# 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 + +libtalerauditor_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined +libtalerauditor_la_SOURCES = \ + curl_defaults.c \ + auditor_api_common.c \ + auditor_api_handle.c auditor_api_handle.h \ + auditor_api_deposit_confirmation.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 + diff --git a/src/auditor-lib/auditor_api_common.c b/src/auditor-lib/auditor_api_common.c new file mode 100644 index 000000000..de05348f3 --- /dev/null +++ b/src/auditor-lib/auditor_api_common.c @@ -0,0 +1,328 @@ +/* + This file is part of TALER + Copyright (C) 2015-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_common.c + * @brief common functions for the exchange API + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include "taler_signatures.h" + + +/** + * Verify a coins transaction history as returned by the exchange. + * + * @param currency expected currency for the coin + * @param coin_pub public key of the coin + * @param history history of the coin in json encoding + * @param[out] total how much of the coin has been spent according to @a history + * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not + */ +int +TALER_EXCHANGE_verify_coin_history (const char *currency, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + json_t *history, + struct TALER_Amount *total) +{ + size_t len; + int add; + struct TALER_Amount rtotal; + + if (NULL == history) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + len = json_array_size (history); + if (0 == len) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + total)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &rtotal)); + for (size_t off=0;off<len;off++) + { + json_t *transaction; + struct TALER_Amount amount; + const char *type; + struct GNUNET_JSON_Specification spec_glob[] = { + TALER_JSON_spec_amount ("amount", + &amount), + GNUNET_JSON_spec_string ("type", + &type), + GNUNET_JSON_spec_end() + }; + + transaction = json_array_get (history, + off); + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec_glob, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + add = GNUNET_SYSERR; + if (0 == strcasecmp (type, + "DEPOSIT")) + { + struct TALER_DepositRequestPS dr; + struct TALER_CoinSpendSignatureP sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &sig), + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", + &dr.h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("h_wire", + &dr.h_wire), + GNUNET_JSON_spec_absolute_time_nbo ("timestamp", + &dr.timestamp), + GNUNET_JSON_spec_absolute_time_nbo ("refund_deadline", + &dr.refund_deadline), + TALER_JSON_spec_amount_nbo ("deposit_fee", + &dr.deposit_fee), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &dr.merchant), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + dr.purpose.size = htonl (sizeof (dr)); + dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); + TALER_amount_hton (&dr.amount_with_fee, + &amount); + dr.coin_pub = *coin_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT, + &dr.purpose, + &sig.eddsa_signature, + &coin_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* TODO: check that deposit fee and coin value match + our expectations from /keys! */ + add = GNUNET_YES; + } + else if (0 == strcasecmp (type, + "MELT")) + { + struct TALER_RefreshMeltCoinAffirmationPS rm; + struct TALER_CoinSpendSignatureP sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &sig), + GNUNET_JSON_spec_fixed_auto ("rc", + &rm.rc), + TALER_JSON_spec_amount_nbo ("melt_fee", + &rm.melt_fee), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rm.purpose.size = htonl (sizeof (rm)); + rm.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); + TALER_amount_hton (&rm.amount_with_fee, + &amount); + rm.coin_pub = *coin_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, + &rm.purpose, + &sig.eddsa_signature, + &coin_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* TODO: check that deposit fee and coin value match + our expectations from /keys! */ + add = GNUNET_YES; + } + else if (0 == strcasecmp (type, + "REFUND")) + { + struct TALER_RefundRequestPS rr; + struct TALER_MerchantSignatureP sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("merchant_sig", + &sig), + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", + &rr.h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &rr.merchant), + GNUNET_JSON_spec_uint64 ("rtransaction_id", + &rr.rtransaction_id), + TALER_JSON_spec_amount_nbo ("refund_fee", + &rr.refund_fee), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rr.purpose.size = htonl (sizeof (rr)); + rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND); + rr.coin_pub = *coin_pub; + TALER_amount_hton (&rr.refund_amount, + &amount); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND, + &rr.purpose, + &sig.eddsa_sig, + &rr.merchant.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* NOTE: theoretically, we could also check that the given + merchant_pub and h_contract_terms appear in the + history under deposits. However, there is really no benefit + for the exchange to lie here, so not checking is probably OK + (an auditor ought to check, though). Then again, we similarly + had no reason to check the merchant's signature (other than a + well-formendess check). */ + /* TODO: check that deposit fee and coin value match + our expectations from /keys! */ + add = GNUNET_NO; + } + else if (0 == strcasecmp (type, + "PAYBACK")) + { + struct TALER_PaybackConfirmationPS pc; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &exchange_pub), + GNUNET_JSON_spec_fixed_auto ("reserve_pub", + &pc.reserve_pub), + GNUNET_JSON_spec_absolute_time_nbo ("timestamp", + &pc.timestamp), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + pc.purpose.size = htonl (sizeof (pc)); + pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PAYBACK); + pc.coin_pub = *coin_pub; + TALER_amount_hton (&pc.payback_amount, + &amount); + 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; + } + add = GNUNET_YES; + } + else + { + /* signature not supported, new version on server? */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_YES == add) + { + /* This amount should be added to the total */ + if (GNUNET_OK != + TALER_amount_add (total, + total, + &amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + else + { + /* This amount should be subtracted from the total. + + However, for the implementation, we first *add* up all of + these negative amounts, as we might get refunds before + deposits from a semi-evil exchange. Then, at the end, we do + the subtraction by calculating "total = total - rtotal" */ + GNUNET_assert (GNUNET_NO == add); + if (GNUNET_OK != + TALER_amount_add (&rtotal, + &rtotal, + &amount)) + { + /* overflow in refund history? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + } + + /* Finally, subtract 'rtotal' from total to handle the subtractions */ + if (GNUNET_OK != + TALER_amount_subtract (total, + total, + &rtotal)) + { + /* underflow in history? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/* end of exchange_api_common.c */ diff --git a/src/auditor-lib/auditor_api_deposit_confirmation.c b/src/auditor-lib/auditor_api_deposit_confirmation.c new file mode 100644 index 000000000..c5d41c45e --- /dev/null +++ b/src/auditor-lib/auditor_api_deposit_confirmation.c @@ -0,0 +1,365 @@ +/* + 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.c + * @brief Implementation of the /deposit request of the auditor'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 "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 json parsed JSON result, NULL on error + */ +static void +handle_deposit_confirmation_finished (void *cls, + long response_code, + const json_t *json) +{ + struct TALER_AUDITOR_DepositConfirmationHandle *dh = cls; + struct TALER_AuditorPublicKeyP auditor_pub; + struct TALER_AuditorPublicKeyP *ep = NULL; + + dh->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + break; + case MHD_HTTP_NOT_FOUND: + 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 dki public key information + * @param amount the amount to be deposit-confirmationed + * @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 auditor) + * @param coin_pub coin’s public key + * @param timestamp timestamp when the deposit-confirmation 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 auditor (can be zero if refunds are not allowed) + * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT_CONFIRMATION 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_Amount *amount, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_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_DepositConfirmationRequestPS dr; + struct TALER_CoinPublicInfo coin_info; + + dr.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_DEPOSIT_CONFIRMATION); + dr.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationRequestPS)); + 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_confirmation_fee, + &dki->fee_deposit_confirmation); + dr.merchant = *merchant_pub; + dr.coin_pub = *coin_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT_CONFIRMATION, + &dr.purpose, + &coin_sig->eddsa_signature, + &coin_pub->eddsa_pub)) + { + GNUNET_break_op (0); + TALER_LOG_WARNING ("Invalid coin signature on /deposit-confirmation request!\n"); + { + TALER_LOG_DEBUG ("... amount_with_fee was %s\n", + TALER_amount2s (amount)); + TALER_LOG_DEBUG ("... deposit-confirmation_fee was %s\n", + TALER_amount2s (&dki->fee_deposit_confirmation)); + } + + 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-confirmation\n"); + return GNUNET_SYSERR; + } + if (0 < TALER_amount_cmp (&dki->fee_deposit_confirmation, + amount)) + { + GNUNET_break_op (0); + TALER_LOG_WARNING ("DepositConfirmation amount smaller than fee\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 amount the amount to be deposit-confirmationed + * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor) + * @param coin_pub coin’s public key + * @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 auditor (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-CONFIRMATION 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_AUDITOR_DepositConfirmationHandle * +TALER_AUDITOR_deposit_confirmation (struct TALER_AUDITOR_Handle *auditor, + const struct GNUNET_HashCode *h_wire, + const struct TALER_Amount *amount_without_fees, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct GNUNET_TIME_Absolute timestamp, + const struct TALER_MerchantPublicKeyP *merchant_pub, + struct GNUNET_TIME_Absolute refund_deadline, + TALER_AUDITOR_DepositConfirmationResultCallback cb, + void *cb_cls) +{ + struct TALER_AUDITOR_DepositConfirmationHandle *dh; + struct GNUNET_CURL_Context *ctx; + json_t *deposit_confirmation_obj; + CURL *eh; + 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 == + MAH_handle_is_ready (auditor)); + if (GNUNET_OK != + verify_signatures (amount, + &h_wire, + h_contract_terms, + coin_pub, + timestamp, + merchant_pub, + refund_deadline, + coin_sig)) + { + GNUNET_break_op (0); + return NULL; + } + + deposit_confirmation_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), + "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), + "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_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"); + dh->depconf.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationConfirmationPS)); + dh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_DEPOSIT_CONFIRMATION); + 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_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, + (GC_JCC) &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_handle.c b/src/auditor-lib/auditor_api_handle.c new file mode 100644 index 000000000..4db528a31 --- /dev/null +++ b/src/auditor-lib/auditor_api_handle.c @@ -0,0 +1,570 @@ +/* + 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 *kr; + + /** + * Task for retrying /version request. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * Key data of the auditor, only valid if + * @e handshake_complete is past stage #MHS_VERSION. + */ + struct TALER_AUDITOR_Version key_data; + + /** + * 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 kr request to free + */ +static void +free_version_request (struct VersionRequest *kr) +{ + GNUNET_free (kr->url); + GNUNET_free (kr); +} + + +/** + * Parse a auditor'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 version + * @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_AUDITOR_AuditorInformation *auditor, + int check_sigs, + json_t *auditor_obj, + const struct TALER_AUDITOR_Version *key_data) +{ + const char *auditor_url; + 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_version", + &version), + 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); + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * 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] 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_version_json (const json_t *resp_obj, + int check_sig, + struct TALER_AUDITOR_Version *key_data, + enum TALER_AUDITOR_VersionCompatibility *vc) +{ + struct TALER_AuditorPublicKeyP pub; + unsigned int age; + unsigned int revision; + unsigned int current; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("version", + &ver), + GNUNET_JSON_spec_fixed_auto ("master_public_key", + &key_data->master_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 (ver, + "%u:%u:%u", + ¤t, + &revision, + &age)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *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; + } + key_data->version = GNUNET_strdup (ver); + return GNUNET_OK; +} + + +/** + * Free key data object. + * + * @param key_data data to free (pointer itself excluded) + */ +static void +free_key_data (struct TALER_AUDITOR_Keys *key_data) +{ + GNUNET_free_non_null (key_data->version); + key_data->version = NULL; +} + + +/** + * 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 json_t *resp_obj) +{ + struct VersionRequest *kr = cls; + struct TALER_AUDITOR_Handle *auditor = kr->auditor; + enum TALER_AUDITOR_VersionCompatibility vc; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received version from URL `%s' with status %ld.\n", + kr->url, + response_code); + vc = TALER_AUDITOR_VC_PROTOCOL_ERROR; + switch (response_code) + { + case 0: + free_version_request (kr); + auditor->kr = 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; + } + /* We keep the denomination version and auditor signatures from the + previous iteration (/version cherry picking) */ + if (GNUNET_OK != + decode_version_json (resp_obj, + GNUNET_YES, + &kd, + &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; + } + auditor->key_data = kd; + + if (MHD_HTTP_OK != response_code) + { + auditor->kr = NULL; + free_version_request (kr); + auditor->state = MHS_FAILED; + free_key_data (&kd_old); + /* notify application that we failed */ + auditor->version_cb (auditor->version_cb_cls, + NULL, + vc); + return; + } + + auditor->kr = NULL; + free_version_request (kr); + auditor->state = MHS_VERSION; + /* notify application about the key information */ + auditor->version_cb (auditor->version_cb_cls, + &auditor->key_data, + vc); + free_key_data (&kd_old); +} + + +/* ********************* 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. "/reserve/withdraw") + * @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. "/reserve/withdraw") + * @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 versionification 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_VersionificationCallback 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 *kr; + CURL *eh; + + auditor->retry_task = NULL; + GNUNET_assert (NULL == auditor->kr); + kr = GNUNET_new (struct VersionRequest); + kr->auditor = auditor; + kr->url = MAH_path_to_url (auditor, + "/version"); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting version 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_HEADERDATA, + kr)); + kr->job = GNUNET_CURL_job_add (auditor->ctx, + eh, + GNUNET_NO, + (GC_JCC) &version_completed_cb, + kr); + auditor->kr = kr; +} + + +/** + * Disconnect from the auditor + * + * @param auditor the auditor handle + */ +void +TALER_AUDITOR_disconnect (struct TALER_AUDITOR_Handle *auditor) +{ + if (NULL != auditor->kr) + { + GNUNET_CURL_job_cancel (auditor->kr->job); + free_version_request (auditor->kr); + auditor->kr = NULL; + } + free_key_data (&auditor->key_data); + 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/auditor-lib/auditor_api_handle.h new file mode 100644 index 000000000..6d7f4cf81 --- /dev/null +++ b/src/auditor-lib/auditor_api_handle.h @@ -0,0 +1,71 @@ +/* + 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 auditor-lib/auditor_api_handle.h + * @brief Internal interface to the handle part of the auditor's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_auditor_service.h" + + +/** + * 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); + + +/** + * 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); + + +/** + * Obtain the URL to use for an API request. + * + * @param h the auditor handle to query + * @param path Taler API path (i.e. "/reserve/withdraw") + * @return the full URL to use with cURL + */ +char * +MAH_path_to_url (struct TALER_AUDITOR_Handle *h, + const char *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. "/reserve/withdraw") + * @return the full URL to use with cURL + */ +char * +MAH_path_to_url2 (const char *base_url, + const char *path); + + +/* end of auditor_api_handle.h */ diff --git a/src/auditor-lib/curl_defaults.c b/src/auditor-lib/curl_defaults.c new file mode 100644 index 000000000..8117fc444 --- /dev/null +++ b/src/auditor-lib/curl_defaults.c @@ -0,0 +1,74 @@ +/* + 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 * +TEL_curl_easy_get (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")); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TCP_FASTOPEN, + 1L)); + + { + /* 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; +} |