skeleton for libtalerauditor

This commit is contained in:
Christian Grothoff 2018-10-22 16:00:06 +02:00
parent 8eda33bbe8
commit e83964badb
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
6 changed files with 1444 additions and 0 deletions

View File

@ -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

View File

@ -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 */

View File

@ -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 merchants 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 coins 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 coins 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 coins 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 coins 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 */

View File

@ -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",
&current,
&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 */

View File

@ -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 */

View File

@ -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;
}