implement #6478, but untested as shown by FIXMEs
This commit is contained in:
parent
26f72f8572
commit
6256bdb15a
@ -1 +1 @@
|
||||
Subproject commit 73f61323554df47079e19cd4236d148e2c17a1b3
|
||||
Subproject commit c36107f80f136f0a512e62c7b992ea18218280bb
|
@ -205,6 +205,24 @@ TALER_amount_cmp (const struct TALER_Amount *a1,
|
||||
const struct TALER_Amount *a2);
|
||||
|
||||
|
||||
/**
|
||||
* Compare the value/fraction of two amounts. Does not compare the currency.
|
||||
* Comparing amounts of different currencies will cause the program to abort().
|
||||
* If unsure, check with #TALER_amount_cmp_currency() first to be sure that
|
||||
* the currencies of the two amounts are identical. NBO variant.
|
||||
*
|
||||
* @param a1 first amount
|
||||
* @param a2 second amount
|
||||
* @return result of the comparison
|
||||
* -1 if `a1 < a2`
|
||||
* 1 if `a1 > a2`
|
||||
* 0 if `a1 == a2`.
|
||||
*/
|
||||
int
|
||||
TALER_amount_cmp_nbo (const struct TALER_AmountNBO *a1,
|
||||
const struct TALER_AmountNBO *a2);
|
||||
|
||||
|
||||
/**
|
||||
* Test if @a a1 and @a a2 are the same currency.
|
||||
*
|
||||
|
@ -516,7 +516,11 @@ TALER_EXCHANGE_verify_coin_history (
|
||||
if (0 == strcasecmp (type,
|
||||
"DEPOSIT"))
|
||||
{
|
||||
struct TALER_DepositRequestPS dr;
|
||||
struct TALER_DepositRequestPS dr = {
|
||||
.purpose.size = htonl (sizeof (dr)),
|
||||
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT),
|
||||
.coin_pub = *coin_pub
|
||||
};
|
||||
struct TALER_CoinSpendSignatureP sig;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_fixed_auto ("coin_sig",
|
||||
@ -546,11 +550,8 @@ TALER_EXCHANGE_verify_coin_history (
|
||||
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,
|
||||
@ -657,7 +658,7 @@ TALER_EXCHANGE_verify_coin_history (
|
||||
GNUNET_JSON_spec_fixed_auto ("merchant_pub",
|
||||
&rr.merchant),
|
||||
GNUNET_JSON_spec_uint64 ("rtransaction_id",
|
||||
&rr.rtransaction_id),
|
||||
&rr.rtransaction_id), // FIXME: shouldn't this be NBO!?
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
|
||||
@ -669,6 +670,7 @@ TALER_EXCHANGE_verify_coin_history (
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
abort (); // FIXME: this shows the case is not tested! ...
|
||||
TALER_amount_hton (&rr.refund_amount,
|
||||
&amount);
|
||||
if (GNUNET_OK !=
|
||||
|
@ -129,6 +129,363 @@ verify_refund_signature_ok (struct TALER_EXCHANGE_RefundHandle *rh,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify that the information in the "409 Conflict" response
|
||||
* from the exchange is valid and indeed shows that the refund
|
||||
* amount requested is too high.
|
||||
*
|
||||
* @param[in,out] rh refund handle (refund fee added)
|
||||
* @param json json reply with the coin transaction history
|
||||
* @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
|
||||
*/
|
||||
static int
|
||||
verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh,
|
||||
const json_t *json)
|
||||
{
|
||||
json_t *history;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_json ("history",
|
||||
&history),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
size_t len;
|
||||
struct TALER_Amount dtotal;
|
||||
bool have_deposit;
|
||||
struct TALER_Amount rtotal;
|
||||
bool have_refund;
|
||||
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_JSON_parse (json,
|
||||
spec,
|
||||
NULL, NULL))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
// FIXME: check that 'history' contains a coin history that
|
||||
// demonstrates that another refund would exceed the deposit amount!
|
||||
|
||||
|
||||
len = json_array_size (history);
|
||||
if (0 == len)
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
have_deposit = false;
|
||||
have_refund = false;
|
||||
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;
|
||||
}
|
||||
if (GNUNET_YES !=
|
||||
TALER_amount_cmp_currency (&amount,
|
||||
&rtotal))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
if (0 == strcasecmp (type,
|
||||
"DEPOSIT"))
|
||||
{
|
||||
struct TALER_DepositRequestPS dr = {
|
||||
.purpose.size = htonl (sizeof (dr)),
|
||||
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT),
|
||||
.coin_pub = rh->depconf.coin_pub
|
||||
};
|
||||
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_fixed_auto ("h_denom_pub",
|
||||
&dr.h_denom_pub),
|
||||
TALER_JSON_spec_absolute_time_nbo ("timestamp",
|
||||
&dr.wallet_timestamp),
|
||||
TALER_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;
|
||||
}
|
||||
TALER_amount_hton (&dr.amount_with_fee,
|
||||
&amount);
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT,
|
||||
&dr,
|
||||
&sig.eddsa_signature,
|
||||
&rh->depconf.coin_pub.eddsa_pub))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
if ( (0 != GNUNET_memcmp (&rh->depconf.h_contract_terms,
|
||||
&dr.h_contract_terms)) ||
|
||||
(0 != GNUNET_memcmp (&rh->depconf.merchant,
|
||||
&dr.merchant)) )
|
||||
{
|
||||
/* deposit information is about a different merchant/contract */
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
if (have_deposit)
|
||||
{
|
||||
/* this cannot really happen, but we conservatively support it anyway */
|
||||
GNUNET_break (0 <=
|
||||
TALER_amount_add (&dtotal,
|
||||
&dtotal,
|
||||
&amount));
|
||||
}
|
||||
else
|
||||
{
|
||||
dtotal = amount;
|
||||
have_deposit = true;
|
||||
}
|
||||
}
|
||||
else if (0 == strcasecmp (type,
|
||||
"REFUND"))
|
||||
{
|
||||
struct TALER_MerchantSignatureP sig;
|
||||
struct TALER_Amount refund_fee;
|
||||
struct TALER_RefundRequestPS rr = {
|
||||
.purpose.size = htonl (sizeof (rr)),
|
||||
.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
|
||||
.coin_pub = rh->depconf.coin_pub
|
||||
};
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
TALER_JSON_spec_amount ("refund_fee",
|
||||
&refund_fee),
|
||||
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), // FIXME: shouldn't this be NBO!?
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_JSON_parse (transaction,
|
||||
spec,
|
||||
NULL, NULL))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
TALER_amount_hton (&rr.refund_amount,
|
||||
&amount);
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
|
||||
&rr,
|
||||
&sig.eddsa_sig,
|
||||
&rr.merchant.eddsa_pub))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
|
||||
if ( (0 != GNUNET_memcmp (&rh->depconf.h_contract_terms,
|
||||
&rr.h_contract_terms)) ||
|
||||
(0 != GNUNET_memcmp (&rh->depconf.merchant,
|
||||
&rr.merchant)) )
|
||||
{
|
||||
/* refund is about a different merchant/contract */
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
if (rr.rtransaction_id == rh->depconf.rtransaction_id)
|
||||
{
|
||||
/* Eh, this shows either a dependency failure or idempotency,
|
||||
but must not happen in a conflict reply. Fail! */
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
|
||||
if (have_refund)
|
||||
{
|
||||
GNUNET_break (0 <=
|
||||
TALER_amount_add (&rtotal,
|
||||
&rtotal,
|
||||
&amount));
|
||||
}
|
||||
else
|
||||
{
|
||||
rtotal = amount;
|
||||
have_refund = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* unexpected type, new version on server? */
|
||||
GNUNET_break_op (0);
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Unexpected type `%s' in response\n",
|
||||
type);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
struct TALER_Amount amount;
|
||||
|
||||
TALER_amount_ntoh (&amount,
|
||||
&rh->depconf.refund_amount);
|
||||
if (0 >
|
||||
TALER_amount_add (&rtotal,
|
||||
&rtotal,
|
||||
&amount))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
}
|
||||
if (-1 == TALER_amount_cmp (&dtotal,
|
||||
&rtotal))
|
||||
{
|
||||
/* dtotal < rtotal: good! */
|
||||
return GNUNET_OK;
|
||||
}
|
||||
/* this fails to prove a conflict */
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify that the information on the "412 Dependency Failed" response
|
||||
* from the exchange is valid and indeed shows that there is a refund
|
||||
* transaction ID reuse going on.
|
||||
*
|
||||
* @param[in,out] rh refund handle (refund fee added)
|
||||
* @param json json reply with the signature
|
||||
* @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
|
||||
*/
|
||||
static int
|
||||
verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh,
|
||||
const json_t *json)
|
||||
{
|
||||
json_t *h;
|
||||
json_t *e;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
GNUNET_JSON_spec_json ("history", &h),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_JSON_parse (json,
|
||||
spec,
|
||||
NULL, NULL))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
if ( (! json_is_array (h)) ||
|
||||
(1 != json_array_size (h) ) )
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
e = json_array_get (h, 0);
|
||||
{
|
||||
struct TALER_Amount amount;
|
||||
const char *type;
|
||||
struct TALER_MerchantSignatureP sig;
|
||||
struct TALER_Amount refund_fee;
|
||||
struct TALER_RefundRequestPS rr = {
|
||||
.purpose.size = htonl (sizeof (rr)),
|
||||
.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
|
||||
.coin_pub = rh->depconf.coin_pub
|
||||
};
|
||||
uint64_t rtransaction_id;
|
||||
struct GNUNET_JSON_Specification spec[] = {
|
||||
TALER_JSON_spec_amount ("amount",
|
||||
&amount),
|
||||
GNUNET_JSON_spec_string ("type",
|
||||
&type),
|
||||
TALER_JSON_spec_amount ("refund_fee",
|
||||
&refund_fee),
|
||||
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",
|
||||
&rtransaction_id),
|
||||
GNUNET_JSON_spec_end ()
|
||||
};
|
||||
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_JSON_parse (e,
|
||||
spec,
|
||||
NULL, NULL))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
rr.rtransaction_id = GNUNET_htonll (rtransaction_id);
|
||||
TALER_amount_hton (&rr.refund_amount,
|
||||
&amount);
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
|
||||
&rr,
|
||||
&sig.eddsa_sig,
|
||||
&rh->depconf.merchant.eddsa_pub))
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
if ( (rr.rtransaction_id != rh->depconf.rtransaction_id) ||
|
||||
(0 != GNUNET_memcmp (&rh->depconf.h_contract_terms,
|
||||
&rr.h_contract_terms)) ||
|
||||
(0 != GNUNET_memcmp (&rh->depconf.merchant,
|
||||
&rr.merchant)) ||
|
||||
(0 == TALER_amount_cmp_nbo (&rh->depconf.refund_amount,
|
||||
&rr.refund_amount)) )
|
||||
{
|
||||
GNUNET_break_op (0);
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
}
|
||||
return GNUNET_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function called when we're done processing the
|
||||
* HTTP /refund request.
|
||||
@ -199,10 +556,17 @@ handle_refund_finished (void *cls,
|
||||
break;
|
||||
case MHD_HTTP_CONFLICT:
|
||||
/* Requested total refunds exceed deposited amount */
|
||||
if (GNUNET_OK !=
|
||||
verify_conflict_history_ok (rh,
|
||||
j))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
hr.ec = TALER_EC_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE;
|
||||
hr.hint = "conflict information provided by exchange is invalid";
|
||||
break;
|
||||
}
|
||||
hr.ec = TALER_JSON_get_error_code (j);
|
||||
hr.hint = TALER_JSON_get_error_hint (j);
|
||||
// FIXME: check that 'history' contains a coin history that
|
||||
// demonstrates that another refund would exceed the deposit amount!
|
||||
break;
|
||||
case MHD_HTTP_GONE:
|
||||
/* Kind of normal: the money was already sent to the merchant
|
||||
@ -211,12 +575,19 @@ handle_refund_finished (void *cls,
|
||||
hr.hint = TALER_JSON_get_error_hint (j);
|
||||
break;
|
||||
case MHD_HTTP_PRECONDITION_FAILED:
|
||||
if (GNUNET_OK !=
|
||||
verify_failed_dependency_ok (rh,
|
||||
j))
|
||||
{
|
||||
GNUNET_break (0);
|
||||
hr.ec = TALER_EC_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE;
|
||||
hr.hint = "failed precondition proof returned by exchange is invalid";
|
||||
break;
|
||||
}
|
||||
/* Two different refund requests were made about the same deposit, but
|
||||
carrying identical refund transaction ids. */
|
||||
hr.ec = TALER_JSON_get_error_code (j);
|
||||
hr.hint = TALER_JSON_get_error_hint (j);
|
||||
// FIXME: check that 'history' contains a duly signed refund request
|
||||
// for the same rtransaction ID but a different amount!
|
||||
break;
|
||||
case MHD_HTTP_INTERNAL_SERVER_ERROR:
|
||||
/* Server had an internal issue; we should retry, but this API
|
||||
|
@ -390,6 +390,35 @@ TALER_amount_cmp (const struct TALER_Amount *a1,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compare the value/fraction of two amounts. Does not compare the currency.
|
||||
* Comparing amounts of different currencies will cause the program to abort().
|
||||
* If unsure, check with #TALER_amount_cmp_currency() first to be sure that
|
||||
* the currencies of the two amounts are identical. NBO variant.
|
||||
*
|
||||
* @param a1 first amount
|
||||
* @param a2 second amount
|
||||
* @return result of the comparison
|
||||
* -1 if `a1 < a2`
|
||||
* 1 if `a1 > a2`
|
||||
* 0 if `a1 == a2`.
|
||||
*/
|
||||
int
|
||||
TALER_amount_cmp_nbo (const struct TALER_AmountNBO *a1,
|
||||
const struct TALER_AmountNBO *a2)
|
||||
{
|
||||
struct TALER_Amount h1;
|
||||
struct TALER_Amount h2;
|
||||
|
||||
TALER_amount_ntoh (&h1,
|
||||
a1);
|
||||
TALER_amount_ntoh (&h2,
|
||||
a2);
|
||||
return TALER_amount_cmp (&h1,
|
||||
&h2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform saturating subtraction of amounts.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user