implement /link signatures

This commit is contained in:
Christian Grothoff 2019-06-26 15:34:14 +02:00
parent 30903b9386
commit 3a2f72b4aa
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
9 changed files with 235 additions and 31 deletions

View File

@ -1,3 +1,8 @@
Wed 26 Jun 2019 03:31:52 PM CEST
Adding link signatures to prevent exchange from tracking
users using coins falsely believed to have been recovered via /link,
bumping protocol version to 4. -CG
Sat 08 Jun 2019 07:54:33 PM CEST
Change payto://sepa/ to payto://iban/ as per current spec. -CG

View File

@ -39,7 +39,7 @@
* release version, and the format is NOT the same that semantic
* versioning uses either.
*/
#define TALER_PROTOCOL_VERSION "3:0:0"
#define TALER_PROTOCOL_VERSION "4:0:0"
/**

View File

@ -92,6 +92,9 @@ handle_link_data (void *cls,
json_object_set_new (obj,
"ev_sig",
GNUNET_JSON_from_rsa_signature (pos->ev_sig.rsa_signature));
json_object_set_new (obj,
"link_sig",
GNUNET_JSON_from_data_auto (&pos->orig_coin_link_sig));
if (0 !=
json_array_append_new (list,
obj))
@ -204,9 +207,9 @@ TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh,
if (GNUNET_OK !=
TEH_DB_run_transaction (connection,
"run link",
&mhd_ret,
&refresh_link_transaction,
&ctx))
&mhd_ret,
&refresh_link_transaction,
&ctx))
{
if (NULL != ctx.mlist)
json_decref (ctx.mlist);

View File

@ -144,6 +144,12 @@ struct RevealContext
*/
const struct TALER_RefreshCoinData *rcds;
/**
* Signatures over the link data (of type
* #TALER_SIGNATURE_WALLET_COIN_LINK)
*/
const struct TALER_CoinSpendSignatureP *link_sigs;
/**
* Envelopes with the signatures to be returned. Initially NULL.
*/
@ -491,6 +497,7 @@ refresh_reveal_persist (void *cls,
struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
rrc->denom_pub = rctx->dkis[i]->denom_pub;
rrc->orig_coin_link_sig = rctx->link_sigs[i];
rrc->coin_ev = rctx->rcds[i].coin_ev;
rrc->coin_ev_size = rctx->rcds[i].coin_ev_size;
rrc->coin_sig = rctx->ev_sigs[i];
@ -524,6 +531,7 @@ refresh_reveal_persist (void *cls,
* @param rctx context for the operation, partially built at this time
* @param transfer_pub transfer public key
* @param tp_json private transfer keys in JSON format
* @param link_sigs_json link signatures in JSON format
* @param new_denoms_h_json requests for fresh coins to be created
* @param coin_evs envelopes of gamma-selected coins to be signed
* @return MHD result code
@ -532,12 +540,14 @@ static int
handle_refresh_reveal_json (struct MHD_Connection *connection,
struct RevealContext *rctx,
const json_t *tp_json,
const json_t *link_sigs_json,
const json_t *new_denoms_h_json,
const json_t *coin_evs)
{
unsigned int num_fresh_coins = json_array_size (new_denoms_h_json);
unsigned int num_tprivs = json_array_size (tp_json);
struct TEH_KS_StateHandle *key_state;
struct TALER_EXCHANGEDB_RefreshMelt refresh_melt;
GNUNET_assert (num_tprivs == TALER_CNC_KAPPA - 1);
if ( (num_fresh_coins >= MAX_FRESH_COINS) ||
@ -545,7 +555,7 @@ handle_refresh_reveal_json (struct MHD_Connection *connection,
{
GNUNET_break_op (0);
return TEH_RESPONSE_reply_arg_invalid (connection,
TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE,
TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE,
"new_denoms");
}
@ -557,6 +567,14 @@ handle_refresh_reveal_json (struct MHD_Connection *connection,
TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISSMATCH,
"new_denoms/coin_evs");
}
if (json_array_size (new_denoms_h_json) !=
json_array_size (link_sigs_json))
{
GNUNET_break_op (0);
return TEH_RESPONSE_reply_arg_invalid (connection,
TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISSMATCH,
"new_denoms/link_sigs");
}
/* Parse transfer private keys array */
for (unsigned int i=0;i<num_tprivs;i++)
@ -579,7 +597,9 @@ handle_refresh_reveal_json (struct MHD_Connection *connection,
/* Resolve denomination hashes */
{
const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dkis[num_fresh_coins];
struct GNUNET_HashCode dki_h[num_fresh_coins];
struct TALER_RefreshCoinData rcds[num_fresh_coins];
struct TALER_CoinSpendSignatureP link_sigs[num_fresh_coins];
int res;
/* Resolve denomination hashes */
@ -596,10 +616,9 @@ handle_refresh_reveal_json (struct MHD_Connection *connection,
/* Parse denomination key hashes */
for (unsigned int i=0;i<num_fresh_coins;i++)
{
struct GNUNET_HashCode dpk_h;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto (NULL,
&dpk_h),
&dki_h[i]),
GNUNET_JSON_spec_end ()
};
@ -614,7 +633,7 @@ handle_refresh_reveal_json (struct MHD_Connection *connection,
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
dkis[i] = TEH_KS_denomination_key_lookup_by_hash (key_state,
&dpk_h,
&dki_h[i],
TEH_KS_DKU_WITHDRAW);
if (NULL == dkis[i])
{
@ -652,9 +671,85 @@ handle_refresh_reveal_json (struct MHD_Connection *connection,
rcd->dk = &dkis[i]->denom_pub;
}
/* lookup old_coin_pub in database */
{
enum GNUNET_DB_QueryStatus qs;
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
(qs = TEH_plugin->get_melt (TEH_plugin->cls,
NULL,
&rctx->rc,
&refresh_melt)))
{
switch (qs)
{
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
res = TEH_RESPONSE_reply_arg_invalid (connection,
TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN,
"rc");
break;
case GNUNET_DB_STATUS_HARD_ERROR:
res = TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR);
break;
case GNUNET_DB_STATUS_SOFT_ERROR:
default:
GNUNET_break (0); /* should be impossible */
res = TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_INTERNAL_INVARIANT_FAILURE);
break;
}
goto cleanup;
}
}
/* Parse link signatures array */
for (unsigned int i=0;i<num_fresh_coins;i++)
{
struct GNUNET_JSON_Specification link_spec[] = {
GNUNET_JSON_spec_fixed_auto (NULL, &link_sigs[i]),
GNUNET_JSON_spec_end ()
};
int res;
res = TEH_PARSE_json_array (connection,
link_sigs_json,
link_spec,
i,
-1);
if (GNUNET_OK != res)
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
/* Check link_sigs[i] signature */
{
struct TALER_LinkDataPS ldp;
ldp.purpose.size = htonl (sizeof (ldp));
ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK);
ldp.h_denom_pub = dki_h[i];
ldp.old_coin_pub = refresh_melt.session.coin.coin_pub;
ldp.transfer_pub = rctx->gamma_tp;
GNUNET_CRYPTO_hash (rcds[i].coin_ev,
rcds[i].coin_ev_size,
&ldp.coin_envelope_hash);
if (GNUNET_OK !=
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK,
&ldp.purpose,
&link_sigs[i].eddsa_signature,
&refresh_melt.session.coin.coin_pub.eddsa_pub))
{
GNUNET_break_op (0);
res = TEH_RESPONSE_reply_signature_invalid (connection,
TALER_EC_REFRESH_REVEAL_LINK_SIGNATURE_INVALID,
"link_sig");
goto cleanup;
}
}
}
rctx->num_fresh_coins = num_fresh_coins;
rctx->rcds = rcds;
rctx->dkis = dkis;
rctx->link_sigs = link_sigs;
/* sign _early_ (optimistic!) to keep out of transaction scope! */
rctx->ev_sigs = GNUNET_new_array (rctx->num_fresh_coins,
@ -749,7 +844,6 @@ handle_refresh_reveal_json (struct MHD_Connection *connection,
}
/**
* Handle a "/refresh/reveal" request. This time, the client reveals
* the private transfer keys except for the cut-and-choose value
@ -777,12 +871,14 @@ TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh,
json_t *root;
json_t *coin_evs;
json_t *transfer_privs;
json_t *link_sigs;
json_t *new_denoms_h;
struct RevealContext rctx;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("rc", &rctx.rc),
GNUNET_JSON_spec_fixed_auto ("transfer_pub", &rctx.gamma_tp),
GNUNET_JSON_spec_json ("transfer_privs", &transfer_privs),
GNUNET_JSON_spec_json ("link_sigs", &link_sigs),
GNUNET_JSON_spec_json ("coin_evs", &coin_evs),
GNUNET_JSON_spec_json ("new_denoms_h", &new_denoms_h),
GNUNET_JSON_spec_end ()
@ -819,12 +915,13 @@ TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh,
GNUNET_JSON_parse_free (spec);
GNUNET_break_op (0);
return TEH_RESPONSE_reply_arg_invalid (connection,
TALER_EC_REFRESH_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID,
TALER_EC_REFRESH_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID,
"transfer_privs");
}
res = handle_refresh_reveal_json (connection,
&rctx,
transfer_privs,
link_sigs,
new_denoms_h,
coin_evs);
GNUNET_JSON_parse_free (spec);

View File

@ -315,6 +315,7 @@ postgres_create_tables (void *cls)
GNUNET_PQ_make_execute("CREATE TABLE IF NOT EXISTS refresh_revealed_coins "
"(rc BYTEA NOT NULL REFERENCES refresh_commitments (rc) ON DELETE CASCADE"
",newcoin_index INT4 NOT NULL"
",link_sig BYTEA NOT NULL CHECK(LENGTH(link_sig)=64)"
",denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE"
",coin_ev BYTEA NOT NULL"
",ev_sig BYTEA NOT NULL"
@ -951,18 +952,20 @@ postgres_prepare (PGconn *db_conn)
"INSERT INTO refresh_revealed_coins "
"(rc "
",newcoin_index "
",link_sig "
",denom_pub_hash "
",coin_ev"
",ev_sig"
") VALUES "
"($1, $2, $3, $4, $5);",
5),
"($1, $2, $3, $4, $5, $6);",
6),
/* Obtain information about the coins created in a refresh
operation, used in #postgres_get_refresh_reveal() */
GNUNET_PQ_make_prepare ("get_refresh_revealed_coins",
"SELECT "
" newcoin_index"
",denom.denom_pub"
",link_sig"
",coin_ev"
",ev_sig"
" FROM refresh_revealed_coins"
@ -1239,6 +1242,7 @@ postgres_prepare (PGconn *db_conn)
" tp.transfer_pub"
",denoms.denom_pub"
",rrc.ev_sig"
",rrc.link_sig"
" FROM refresh_commitments"
" JOIN refresh_revealed_coins rrc"
" USING (rc)"
@ -3641,7 +3645,7 @@ postgres_select_refunds_by_coin (void *cls,
* Lookup refresh melt commitment data under the given @a rc.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
* @param session database handle to use
* @param session database handle to use, NULL if not run in any transaction
* @param rc commitment hash to use to locate the operation
* @param[out] refresh_melt where to store the result
* @return transaction status
@ -3652,6 +3656,7 @@ postgres_get_melt (void *cls,
const struct TALER_RefreshCommitmentP *rc,
struct TALER_EXCHANGEDB_RefreshMelt *refresh_melt)
{
struct PostgresClosure *pc = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (rc),
GNUNET_PQ_query_param_end
@ -3675,6 +3680,8 @@ postgres_get_melt (void *cls,
};
enum GNUNET_DB_QueryStatus qs;
if (NULL == session)
session = postgres_get_session (pc);
qs = GNUNET_PQ_eval_prepared_singleton_select (session->conn,
"get_melt",
params,
@ -3790,6 +3797,7 @@ postgres_insert_refresh_reveal (void *cls,
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (rc),
GNUNET_PQ_query_param_uint32 (&i),
GNUNET_PQ_query_param_auto_from_type (&rrc->orig_coin_link_sig),
GNUNET_PQ_query_param_auto_from_type (&denom_pub_hash),
GNUNET_PQ_query_param_fixed_size (rrc->coin_ev,
rrc->coin_ev_size),
@ -3876,6 +3884,8 @@ add_revealed_coins (void *cls,
&off),
GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
&rrc->denom_pub.rsa_public_key),
GNUNET_PQ_result_spec_auto_from_type ("link_sig",
&rrc->orig_coin_link_sig),
GNUNET_PQ_result_spec_variable_size ("coin_ev",
(void **) &rrc->coin_ev,
&rrc->coin_ev_size),
@ -3932,7 +3942,7 @@ postgres_get_refresh_reveal (void *cls,
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("transfer_pub",
&tp),
&tp),
GNUNET_PQ_result_spec_variable_size ("transfer_privs",
&tpriv,
&tpriv_size),
@ -4087,8 +4097,8 @@ free_link_data_list (void *cls,
*/
static void
add_ldl (void *cls,
PGresult *result,
unsigned int num_results)
PGresult *result,
unsigned int num_results)
{
struct LinkDataContext *ldctx = cls;
@ -4102,11 +4112,13 @@ add_ldl (void *cls,
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("transfer_pub",
&transfer_pub),
GNUNET_PQ_result_spec_rsa_signature ("ev_sig",
&pos->ev_sig.rsa_signature),
GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
&pos->denom_pub.rsa_public_key),
GNUNET_PQ_result_spec_end
GNUNET_PQ_result_spec_auto_from_type ("link_sig",
&pos->orig_coin_link_sig),
GNUNET_PQ_result_spec_rsa_signature ("ev_sig",
&pos->ev_sig.rsa_signature),
GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
&pos->denom_pub.rsa_public_key),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
@ -4173,10 +4185,10 @@ postgres_get_link_data (void *cls,
ldctx.last = NULL;
ldctx.status = GNUNET_OK;
qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
"get_link",
params,
&add_ldl,
&ldctx);
"get_link",
params,
&add_ldl,
&ldctx);
if (NULL != ldctx.last)
{
if (GNUNET_OK == ldctx.status)

View File

@ -617,7 +617,14 @@ enum TALER_ErrorCode
*/
TALER_EC_REFRESH_REVEAL_FRESH_DENOMINATION_KEY_NOT_FOUND = 1361,
/**
* The signature made with the coin over the link data is invalid.
* This response is provided with HTTP status code
* MHD_HTTP_BAD_REQUEST.
*/
TALER_EC_REFRESH_REVEAL_LINK_SIGNATURE_INVALID = 1362,
/**
* The coin specified in the link request is unknown to the exchange.
* This response is provided with HTTP status code

View File

@ -541,6 +541,12 @@ struct TALER_EXCHANGEDB_LinkDataList
* Signature over the blinded envelope.
*/
struct TALER_DenominationSignature ev_sig;
/**
* Signature of the original coin being refreshed over the
* link data, of type #TALER_SIGNATURE_WALLET_COIN_LINK
*/
struct TALER_CoinSpendSignatureP orig_coin_link_sig;
};
@ -793,6 +799,12 @@ struct TALER_EXCHANGEDB_RefreshRevealedCoin
*/
struct TALER_DenominationPublicKey denom_pub;
/**
* Signature of the original coin being refreshed over the
* link data, of type #TALER_SIGNATURE_WALLET_COIN_LINK
*/
struct TALER_CoinSpendSignatureP orig_coin_link_sig;
/**
* Blinded message to be signed (in envelope), with @e coin_env_size bytes.
*/
@ -1634,7 +1646,7 @@ struct TALER_EXCHANGEDB_Plugin
/**
* Lookup refresh metl commitment data under the given @a rc.
* Lookup refresh melt commitment data under the given @a rc.
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param session database handle to use

View File

@ -130,9 +130,9 @@
#define TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED 1040
/*********************/
/* Wallet signatures */
/*********************/
/**********************/
/* Auditor signatures */
/**********************/
/**
* Signature where the auditor confirms that he is
@ -209,6 +209,11 @@
*/
#define TALER_SIGNATURE_WALLET_COIN_PAYBACK 1203
/**
* Signature using a coin key authenticating link data.
*/
#define TALER_SIGNATURE_WALLET_COIN_LINK 1204
/*******************/
/* Test signatures */
@ -228,6 +233,41 @@
GNUNET_NETWORK_STRUCT_BEGIN
/**
* @brief Format used for to allow the wallet to authenticate
* link data provided by the exchange.
*/
struct TALER_LinkDataPS
{
/**
* Purpose must be #TALER_SIGNATURE_WALLET_COIN_LINK.
* Used with an EdDSA signature of a `struct TALER_CoinPublicKeyP`.
*/
struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
/**
* Hash of the denomination public key of the new coin.
*/
struct GNUNET_HashCode h_denom_pub;
/**
* Public key of the old coin being refreshed.
*/
struct TALER_CoinSpendPublicKeyP old_coin_pub;
/**
* Transfer public key (for which the private key was not revealed)
*/
struct TALER_TransferPublicKeyP transfer_pub;
/**
* Hash of the blinded new coin.
*/
struct GNUNET_HashCode coin_envelope_hash;
};
/**
* @brief Format used for to generate the signature on a request to withdraw
* coins from a reserve.

View File

@ -1065,7 +1065,7 @@ handle_refresh_melt_finished (void *cls,
{
rmh->melt_cb (rmh->melt_cb_cls,
response_code,
TALER_JSON_get_error_code (j),
TALER_JSON_get_error_code (j),
noreveal_index,
(0 == response_code) ? NULL : &exchange_pub,
j);
@ -1534,6 +1534,7 @@ TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange,
json_t *new_denoms_h;
json_t *coin_evs;
json_t *reveal_obj;
json_t *link_sigs;
CURL *eh;
struct GNUNET_CURL_Context *ctx;
struct MeltData *md;
@ -1565,6 +1566,7 @@ TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange,
/* now new_denoms */
GNUNET_assert (NULL != (new_denoms_h = json_array ()));
GNUNET_assert (NULL != (coin_evs = json_array ()));
GNUNET_assert (NULL != (link_sigs = json_array ()));
for (unsigned int i=0;i<md->num_fresh_coins;i++)
{
struct GNUNET_HashCode denom_hash;
@ -1591,6 +1593,30 @@ TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange,
json_array_append_new (coin_evs,
GNUNET_JSON_from_data (pd.coin_ev,
pd.coin_ev_size)));
/* compute link signature */
{
struct TALER_CoinSpendSignatureP link_sig;
struct TALER_LinkDataPS ldp;
ldp.purpose.size = htonl (sizeof (ldp));
ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK);
ldp.h_denom_pub = denom_hash;
GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv,
&ldp.old_coin_pub.eddsa_pub);
ldp.transfer_pub = transfer_pub;
GNUNET_CRYPTO_hash (pd.coin_ev,
pd.coin_ev_size,
&ldp.coin_envelope_hash);
GNUNET_assert (GNUNET_OK ==
GNUNET_CRYPTO_eddsa_sign (&md->melted_coin.coin_priv.eddsa_priv,
&ldp.purpose,
&link_sig.eddsa_signature));
GNUNET_assert (0 ==
json_array_append_new (link_sigs,
GNUNET_JSON_from_data_auto (&link_sig)));
}
GNUNET_free (pd.coin_ev);
}
@ -1610,13 +1636,15 @@ TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange,
}
/* build main JSON request */
reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o}",
reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}",
"rc",
GNUNET_JSON_from_data_auto (&md->rc),
"transfer_pub",
GNUNET_JSON_from_data_auto (&transfer_pub),
"transfer_privs",
transfer_privs,
"link_sigs",
link_sigs,
"new_denoms_h",
new_denoms_h,
"coin_evs",