From 3a2f72b4aad3c2719e4326d30a97e49e26b5d797 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 26 Jun 2019 15:34:14 +0200 Subject: [PATCH] implement /link signatures --- ChangeLog | 5 + src/exchange/taler-exchange-httpd_keystate.c | 2 +- .../taler-exchange-httpd_refresh_link.c | 9 +- .../taler-exchange-httpd_refresh_reveal.c | 109 +++++++++++++++++- src/exchangedb/plugin_exchangedb_postgres.c | 42 ++++--- src/include/taler_error_codes.h | 7 ++ src/include/taler_exchangedb_plugin.h | 14 ++- src/include/taler_signatures.h | 46 +++++++- src/lib/exchange_api_refresh.c | 32 ++++- 9 files changed, 235 insertions(+), 31 deletions(-) diff --git a/ChangeLog b/ChangeLog index 468fad8a5..61ee3b6f6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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 diff --git a/src/exchange/taler-exchange-httpd_keystate.c b/src/exchange/taler-exchange-httpd_keystate.c index 6134faf34..c4e550210 100644 --- a/src/exchange/taler-exchange-httpd_keystate.c +++ b/src/exchange/taler-exchange-httpd_keystate.c @@ -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" /** diff --git a/src/exchange/taler-exchange-httpd_refresh_link.c b/src/exchange/taler-exchange-httpd_refresh_link.c index 0ec505a84..04145f48d 100644 --- a/src/exchange/taler-exchange-httpd_refresh_link.c +++ b/src/exchange/taler-exchange-httpd_refresh_link.c @@ -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); diff --git a/src/exchange/taler-exchange-httpd_refresh_reveal.c b/src/exchange/taler-exchange-httpd_refresh_reveal.c index 6fc8d1c5e..2f6d0b14e 100644 --- a/src/exchange/taler-exchange-httpd_refresh_reveal.c +++ b/src/exchange/taler-exchange-httpd_refresh_reveal.c @@ -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;idk = &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;igamma_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); diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 4945d5a56..9f0f044b4 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -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) diff --git a/src/include/taler_error_codes.h b/src/include/taler_error_codes.h index fbd983520..5767a73b2 100644 --- a/src/include/taler_error_codes.h +++ b/src/include/taler_error_codes.h @@ -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 diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 6f1625dd0..67ebc62fc 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -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 diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h index bff73f737..b738e3156 100644 --- a/src/include/taler_signatures.h +++ b/src/include/taler_signatures.h @@ -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. diff --git a/src/lib/exchange_api_refresh.c b/src/lib/exchange_api_refresh.c index e2a3a245a..61bee6d64 100644 --- a/src/lib/exchange_api_refresh.c +++ b/src/lib/exchange_api_refresh.c @@ -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;inum_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",