test coin_priv re-use with deposit and refresh, update handling of the error code client-side

This commit is contained in:
Christian Grothoff 2020-07-10 23:09:46 +02:00
parent ddf95c491a
commit 7085cfef70
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
4 changed files with 249 additions and 16 deletions

View File

@ -1295,6 +1295,30 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
unsigned int expected_response_code);
/**
* Create a withdraw command, letting the caller specify
* the desired amount as string and also re-using an existing
* coin private key in the process (violating the specification,
* which will result in an error when spending the coin!).
*
* @param label command label.
* @param reserve_reference command providing us with a reserve to withdraw from
* @param amount how much we withdraw.
* @param coin_ref reference to (withdraw/reveal) command of a coin
* from which we should re-use the private key
* @param expected_response_code which HTTP response code
* we expect from the exchange.
* @return the withdraw command to be executed by the interpreter.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_withdraw_amount_reuse_key (
const char *label,
const char *reserve_reference,
const char *amount,
const char *coin_ref,
unsigned int expected_response_code);
/**
* Create withdraw command, letting the caller specify the
* amount by a denomination key.

View File

@ -75,6 +75,11 @@ struct TALER_EXCHANGE_MeltHandle
*/
struct MeltData *md;
/**
* Public key of the coin being melted.
*/
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
* @brief Public information about the coin's denomination key
*/
@ -153,6 +158,48 @@ verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
}
/**
* Verify that the signatures on the "409 CONFLICT" response from the
* exchange demonstrating customer denomination key differences
* resulting from coin private key reuse are valid.
*
* @param mh melt handle
* @param json json reply with the signature(s) and transaction history
* @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not
*/
static int
verify_melt_signature_denom_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
const json_t *json)
{
json_t *history;
struct TALER_Amount total;
struct GNUNET_HashCode h_denom_pub;
memset (&h_denom_pub,
0,
sizeof (h_denom_pub));
history = json_object_get (json,
"history");
if (GNUNET_OK !=
TALER_EXCHANGE_verify_coin_history (&mh->dki,
mh->dki.value.currency,
&mh->coin_pub,
history,
&h_denom_pub,
&total))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (0 != GNUNET_memcmp (&mh->dki.h_key,
&h_denom_pub))
return GNUNET_OK; /* indeed, proof with different denomination key provided */
/* invalid proof provided */
return GNUNET_SYSERR;
}
/**
* Verify that the signatures on the "409 CONFLICT" response from the
* exchange demonstrating customer double-spending are valid.
@ -162,8 +209,8 @@ verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
* @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not
*/
static int
verify_melt_signature_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
const json_t *json)
verify_melt_signature_spend_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
const json_t *json)
{
json_t *history;
struct TALER_Amount original_value;
@ -329,15 +376,38 @@ handle_melt_finished (void *cls,
hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_CONFLICT:
/* Double spending; check signatures on transaction history */
if (GNUNET_OK !=
verify_melt_signature_conflict (mh,
j))
hr.ec = TALER_JSON_get_error_code (j);
switch (hr.ec)
{
case TALER_EC_MELT_INSUFFICIENT_FUNDS:
/* Double spending; check signatures on transaction history */
if (GNUNET_OK !=
verify_melt_signature_spend_conflict (mh,
j))
{
GNUNET_break_op (0);
hr.http_status = 0;
hr.ec = TALER_EC_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
hr.hint = TALER_JSON_get_error_hint (j);
}
break;
case TALER_EC_COIN_CONFLICTING_DENOMINATION_KEY:
if (GNUNET_OK !=
verify_melt_signature_denom_conflict (mh,
j))
{
GNUNET_break_op (0);
hr.http_status = 0;
hr.ec = TALER_EC_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
hr.hint = TALER_JSON_get_error_hint (j);
}
break;
default:
GNUNET_break_op (0);
hr.http_status = 0;
hr.ec = TALER_EC_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
hr.hint = TALER_JSON_get_error_hint (j);
break;
}
break;
case MHD_HTTP_FORBIDDEN:
@ -485,6 +555,7 @@ TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
/* and now we can at last begin the actual request handling */
mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle);
mh->exchange = exchange;
mh->coin_pub = melt.coin_pub;
mh->dki = *dki;
mh->dki.key.rsa_public_key = NULL; /* lifetime not warranted, so better
not copy the pointer */

View File

@ -136,12 +136,12 @@ run (void *cls,
* Do another transfer to the same reserve
*/
TALER_TESTING_cmd_admin_add_incoming_with_ref ("create-reserve-1.2",
"EUR:1",
"EUR:2.01",
&bc.exchange_auth,
bc.user42_payto,
"create-reserve-1"),
TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-1.2",
"EUR:1",
"EUR:2.01",
bc.user42_payto,
bc.exchange_payto,
"create-reserve-1.2"),
@ -153,6 +153,15 @@ run (void *cls,
"create-reserve-1",
"EUR:5",
MHD_HTTP_OK),
/**
* Withdraw EUR:1 using the SAME private coin key as for the previous coin
* (in violation of the specification, to be detected on spending!).
*/
TALER_TESTING_cmd_withdraw_amount_reuse_key ("withdraw-coin-1x",
"create-reserve-1",
"EUR:1",
"withdraw-coin-1",
MHD_HTTP_OK),
/**
* Check the reserve is depleted.
*/
@ -160,6 +169,13 @@ run (void *cls,
"create-reserve-1",
"EUR:0",
MHD_HTTP_OK),
/*
* Try to overdraw.
*/
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
"create-reserve-1",
"EUR:5",
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_end ()
};
@ -178,13 +194,14 @@ run (void *cls,
TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay",
"deposit-simple",
MHD_HTTP_OK),
/*
* Try to overdraw.
*/
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
"create-reserve-1",
"EUR:5",
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_deposit ("deposit-reused-coin-key-failure",
"withdraw-coin-1x",
0,
bc.user42_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
MHD_HTTP_CONFLICT),
/**
* Try to double spend using different wire details.
*/
@ -225,6 +242,14 @@ run (void *cls,
};
struct TALER_TESTING_Command refresh[] = {
/**
* Try to melt the coin that shared the private key with another
* coin (should fail). */
TALER_TESTING_cmd_melt ("refresh-melt-reused-coin-key-failure",
"withdraw-coin-1x",
MHD_HTTP_CONFLICT,
NULL),
/* Fill reserve with EUR:5, 1ct is for fees. */
CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-1",
"EUR:5.01"),

View File

@ -59,6 +59,12 @@ struct WithdrawState
*/
const char *reserve_reference;
/**
* Reference to a withdraw or reveal operation from which we should
* re-use the private coin key, or NULL for regular withdrawal.
*/
const char *reuse_coin_key_ref;
/**
* String describing the denomination value we should withdraw.
* A corresponding denomination key must exist in the exchange's
@ -274,6 +280,50 @@ reserve_withdraw_cb (void *cls,
}
/**
* Parser reference to a coin.
*
* @param coin_reference of format $LABEL['#' $INDEX]?
* @param[out] cref where we return a copy of $LABEL
* @param[out] idx where we set $INDEX
* @return #GNUNET_SYSERR if $INDEX is present but not numeric
*/
static int
parse_coin_reference (const char *coin_reference,
char **cref,
unsigned int *idx)
{
const char *index;
/* We allow command references of the form "$LABEL#$INDEX" or
just "$LABEL", which implies the index is 0. Figure out
which one it is. */
index = strchr (coin_reference, '#');
if (NULL == index)
{
*idx = 0;
*cref = GNUNET_strdup (coin_reference);
return GNUNET_OK;
}
*cref = GNUNET_strndup (coin_reference,
index - coin_reference);
if (1 != sscanf (index + 1,
"%u",
idx))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Numeric index (not `%s') required after `#' in command reference of command in %s:%u\n",
index,
__FILE__,
__LINE__);
GNUNET_free (*cref);
*cref = NULL;
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Run the command.
*/
@ -307,7 +357,32 @@ withdraw_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
TALER_planchet_setup_random (&ws->ps);
if (NULL == ws->reuse_coin_key_ref)
{
TALER_planchet_setup_random (&ws->ps);
}
else
{
const struct TALER_CoinSpendPrivateKeyP *coin_priv;
const struct TALER_TESTING_Command *cref;
char *cstr;
unsigned int index;
GNUNET_assert (GNUNET_OK ==
parse_coin_reference (ws->reuse_coin_key_ref,
&cstr,
&index));
cref = TALER_TESTING_interpreter_lookup_command (is,
cstr);
GNUNET_assert (NULL != cref);
GNUNET_free (cstr);
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_coin_priv (cref,
index,
&coin_priv));
TALER_planchet_setup_random (&ws->ps);
ws->ps.coin_priv = *coin_priv;
}
ws->is = is;
if (NULL == ws->pk)
{
@ -526,6 +601,44 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
}
/**
* Create a withdraw command, letting the caller specify
* the desired amount as string and also re-using an existing
* coin private key in the process (violating the specification,
* which will result in an error when spending the coin!).
*
* @param label command label.
* @param reserve_reference command providing us with a reserve to withdraw from
* @param amount how much we withdraw.
* @param coin_ref reference to (withdraw/reveal) command of a coin
* from which we should re-use the private key
* @param expected_response_code which HTTP response code
* we expect from the exchange.
* @return the withdraw command to be executed by the interpreter.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_withdraw_amount_reuse_key (
const char *label,
const char *reserve_reference,
const char *amount,
const char *coin_ref,
unsigned int expected_response_code)
{
struct TALER_TESTING_Command cmd;
cmd = TALER_TESTING_cmd_withdraw_amount (label,
reserve_reference,
amount,
expected_response_code);
{
struct WithdrawState *ws = cmd.cls;
ws->reuse_coin_key_ref = coin_ref;
}
return cmd;
}
/**
* Create withdraw command, letting the caller specify the
* amount by a denomination key.