diff options
Diffstat (limited to 'src/auditor-lib')
| -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 00000000..de239996 --- /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 00000000..de05348f --- /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 00000000..c5d41c45 --- /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 00000000..4db528a3 --- /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 00000000..6d7f4cf8 --- /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 00000000..8117fc44 --- /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; +}  | 
