taler-exchange-httpd_post-recoup-refresh.c (14107B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2017-2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-exchange-httpd_post-recoup-refresh.c 18 * @brief Handle /recoup-refresh requests; parses the POST and JSON and 19 * verifies the coin signature before handing things off 20 * to the database. 21 * @author Christian Grothoff 22 */ 23 #include "taler/platform.h" 24 #include <gnunet/gnunet_util_lib.h> 25 #include <gnunet/gnunet_json_lib.h> 26 #include <jansson.h> 27 #include <microhttpd.h> 28 #include <pthread.h> 29 #include "taler/taler_json_lib.h" 30 #include "taler/taler_mhd_lib.h" 31 #include "taler-exchange-httpd_db.h" 32 #include "taler-exchange-httpd_post-recoup-refresh.h" 33 #include "taler-exchange-httpd_responses.h" 34 #include "taler-exchange-httpd_get-keys.h" 35 #include "taler/taler_exchangedb_lib.h" 36 37 38 /** 39 * Closure for #recoup_refresh_transaction(). 40 */ 41 struct RecoupContext 42 { 43 44 /** 45 * Set by #recoup_transaction() to the old coin that will 46 * receive the recoup. 47 */ 48 struct TALER_CoinSpendPublicKeyP old_coin_pub; 49 50 /** 51 * Details about the coin. 52 */ 53 const struct TALER_CoinPublicInfo *coin; 54 55 /** 56 * Key used to blind the coin. 57 */ 58 const union GNUNET_CRYPTO_BlindingSecretP *coin_bks; 59 60 /** 61 * Signature of the coin requesting recoup. 62 */ 63 const struct TALER_CoinSpendSignatureP *coin_sig; 64 65 /** 66 * Unique ID of the coin in the known_coins table. 67 */ 68 uint64_t known_coin_id; 69 70 /** 71 * Unique ID of the refresh reveal context of the melt for the new coin. 72 */ 73 uint64_t rrc_serial; 74 75 /** 76 * Set by #recoup_transaction to the timestamp when the recoup 77 * was accepted. 78 */ 79 struct GNUNET_TIME_Timestamp now; 80 81 }; 82 83 84 /** 85 * Execute a "recoup-refresh". The validity of the coin and signature have 86 * already been checked. The database must now check that the coin is not 87 * (double) spent, and execute the transaction. 88 * 89 * IF it returns a non-error code, the transaction logic MUST 90 * NOT queue a MHD response. IF it returns an hard error, the 91 * transaction logic MUST queue a MHD response and set @a mhd_ret. IF 92 * it returns the soft error code, the function MAY be called again to 93 * retry and MUST not queue a MHD response. 94 * 95 * @param cls the `struct RecoupContext *` 96 * @param connection MHD request which triggered the transaction 97 * @param[out] mhd_ret set to MHD response status for @a connection, 98 * if transaction failed (!) 99 * @return transaction status code 100 */ 101 static enum GNUNET_DB_QueryStatus 102 recoup_refresh_transaction (void *cls, 103 struct MHD_Connection *connection, 104 MHD_RESULT *mhd_ret) 105 { 106 struct RecoupContext *pc = cls; 107 enum GNUNET_DB_QueryStatus qs; 108 bool recoup_ok; 109 bool internal_failure; 110 111 /* Finally, store new refund data */ 112 pc->now = GNUNET_TIME_timestamp_get (); 113 qs = TEH_plugin->do_recoup_refresh (TEH_plugin->cls, 114 &pc->old_coin_pub, 115 pc->rrc_serial, 116 pc->coin_bks, 117 &pc->coin->coin_pub, 118 pc->known_coin_id, 119 pc->coin_sig, 120 &pc->now, 121 &recoup_ok, 122 &internal_failure); 123 if (0 > qs) 124 { 125 if (GNUNET_DB_STATUS_HARD_ERROR == qs) 126 *mhd_ret = TALER_MHD_reply_with_error ( 127 connection, 128 MHD_HTTP_INTERNAL_SERVER_ERROR, 129 TALER_EC_GENERIC_DB_FETCH_FAILED, 130 "do_recoup_refresh"); 131 return qs; 132 } 133 134 if (internal_failure) 135 { 136 GNUNET_break (0); 137 *mhd_ret = TALER_MHD_reply_with_error ( 138 connection, 139 MHD_HTTP_INTERNAL_SERVER_ERROR, 140 TALER_EC_GENERIC_DB_INVARIANT_FAILURE, 141 "coin transaction history"); 142 return GNUNET_DB_STATUS_HARD_ERROR; 143 } 144 if (! recoup_ok) 145 { 146 *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds ( 147 connection, 148 TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS, 149 &pc->coin->denom_pub_hash, 150 &pc->coin->coin_pub); 151 return GNUNET_DB_STATUS_HARD_ERROR; 152 } 153 return qs; 154 } 155 156 157 /** 158 * We have parsed the JSON information about the recoup request. Do 159 * some basic sanity checks (especially that the signature on the 160 * request and coin is valid) and then execute the recoup operation. 161 * Note that we need the DB to check the fee structure, so this is not 162 * done here but during the recoup_transaction(). 163 * 164 * @param connection the MHD connection to handle 165 * @param coin information about the coin 166 * @param exchange_vals values contributed by the exchange 167 * during refresh 168 * @param coin_bks blinding data of the coin (to be checked) 169 * @param nonce withdraw nonce (if CS is used) 170 * @param coin_sig signature of the coin 171 * @return MHD result code 172 */ 173 static MHD_RESULT 174 verify_and_execute_recoup_refresh ( 175 struct MHD_Connection *connection, 176 const struct TALER_CoinPublicInfo *coin, 177 const struct TALER_ExchangeBlindingValues *exchange_vals, 178 const union GNUNET_CRYPTO_BlindingSecretP *coin_bks, 179 const union GNUNET_CRYPTO_BlindSessionNonce *nonce, 180 const struct TALER_CoinSpendSignatureP *coin_sig) 181 { 182 struct RecoupContext pc; 183 const struct TEH_DenominationKey *dk; 184 MHD_RESULT mret; 185 struct TALER_BlindedCoinHashP h_blind; 186 187 /* check denomination exists and is in recoup mode */ 188 dk = TEH_keys_denomination_by_hash (&coin->denom_pub_hash, 189 connection, 190 &mret); 191 if (NULL == dk) 192 return mret; 193 if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time)) 194 { 195 /* This denomination is past the expiration time for recoup */ 196 return TEH_RESPONSE_reply_expired_denom_pub_hash ( 197 connection, 198 &coin->denom_pub_hash, 199 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, 200 "RECOUP-REFRESH"); 201 } 202 if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time)) 203 { 204 /* This denomination is not yet valid */ 205 return TEH_RESPONSE_reply_expired_denom_pub_hash ( 206 connection, 207 &coin->denom_pub_hash, 208 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, 209 "RECOUP-REFRESH"); 210 } 211 if (! dk->recoup_possible) 212 { 213 /* This denomination is not eligible for recoup */ 214 return TEH_RESPONSE_reply_expired_denom_pub_hash ( 215 connection, 216 &coin->denom_pub_hash, 217 TALER_EC_EXCHANGE_RECOUP_REFRESH_NOT_ELIGIBLE, 218 "RECOUP-REFRESH"); 219 } 220 221 /* check denomination signature */ 222 switch (dk->denom_pub.bsign_pub_key->cipher) 223 { 224 case GNUNET_CRYPTO_BSA_RSA: 225 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++; 226 break; 227 case GNUNET_CRYPTO_BSA_CS: 228 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++; 229 break; 230 default: 231 break; 232 } 233 if (GNUNET_YES != 234 TALER_test_coin_valid (coin, 235 &dk->denom_pub)) 236 { 237 TALER_LOG_WARNING ("Invalid coin passed for recoup\n"); 238 return TALER_MHD_reply_with_error ( 239 connection, 240 MHD_HTTP_FORBIDDEN, 241 TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID, 242 NULL); 243 } 244 245 /* check recoup request signature */ 246 TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; 247 if (GNUNET_OK != 248 TALER_wallet_recoup_refresh_verify (&coin->denom_pub_hash, 249 coin_bks, 250 &coin->coin_pub, 251 coin_sig)) 252 { 253 GNUNET_break_op (0); 254 return TALER_MHD_reply_with_error ( 255 connection, 256 MHD_HTTP_FORBIDDEN, 257 TALER_EC_EXCHANGE_RECOUP_REFRESH_SIGNATURE_INVALID, 258 NULL); 259 } 260 261 { 262 struct TALER_CoinPubHashP c_hash; 263 struct TALER_BlindedPlanchet blinded_planchet; 264 265 if (GNUNET_OK != 266 TALER_denom_blind (&dk->denom_pub, 267 coin_bks, 268 nonce, 269 &coin->h_age_commitment, 270 &coin->coin_pub, 271 exchange_vals, 272 &c_hash, 273 &blinded_planchet)) 274 { 275 GNUNET_break (0); 276 return TALER_MHD_reply_with_error ( 277 connection, 278 MHD_HTTP_INTERNAL_SERVER_ERROR, 279 TALER_EC_EXCHANGE_RECOUP_REFRESH_BLINDING_FAILED, 280 NULL); 281 } 282 TALER_coin_ev_hash (&blinded_planchet, 283 &coin->denom_pub_hash, 284 &h_blind); 285 TALER_blinded_planchet_free (&blinded_planchet); 286 } 287 288 pc.coin_sig = coin_sig; 289 pc.coin_bks = coin_bks; 290 pc.coin = coin; 291 292 { 293 MHD_RESULT mhd_ret = MHD_NO; 294 enum GNUNET_DB_QueryStatus qs; 295 296 /* make sure coin is 'known' in database */ 297 qs = TEH_make_coin_known (coin, 298 connection, 299 &pc.known_coin_id, 300 &mhd_ret); 301 /* no transaction => no serialization failures should be possible */ 302 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 303 if (qs < 0) 304 return mhd_ret; 305 } 306 307 { 308 enum GNUNET_DB_QueryStatus qs; 309 310 qs = TEH_plugin->get_old_coin_by_h_blind (TEH_plugin->cls, 311 &h_blind, 312 &pc.old_coin_pub, 313 &pc.rrc_serial); 314 if (0 > qs) 315 { 316 GNUNET_break (0); 317 return TALER_MHD_reply_with_error ( 318 connection, 319 MHD_HTTP_INTERNAL_SERVER_ERROR, 320 TALER_EC_GENERIC_DB_FETCH_FAILED, 321 "get_old_coin_by_h_blind"); 322 } 323 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 324 { 325 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 326 "Recoup-refresh requested for unknown envelope %s\n", 327 GNUNET_h2s (&h_blind.hash)); 328 return TALER_MHD_reply_with_error ( 329 connection, 330 MHD_HTTP_NOT_FOUND, 331 TALER_EC_EXCHANGE_RECOUP_REFRESH_MELT_NOT_FOUND, 332 NULL); 333 } 334 } 335 336 /* Perform actual recoup transaction */ 337 { 338 MHD_RESULT mhd_ret; 339 340 if (GNUNET_OK != 341 TEH_DB_run_transaction (connection, 342 "run recoup-refresh", 343 TEH_MT_REQUEST_OTHER, 344 &mhd_ret, 345 &recoup_refresh_transaction, 346 &pc)) 347 return mhd_ret; 348 } 349 /* Recoup succeeded, return result */ 350 return TALER_MHD_REPLY_JSON_PACK (connection, 351 MHD_HTTP_OK, 352 GNUNET_JSON_pack_data_auto ( 353 "old_coin_pub", 354 &pc.old_coin_pub)); 355 } 356 357 358 /** 359 * Handle a "/coins/$COIN_PUB/recoup-refresh" request. Parses the JSON, and, if 360 * successful, passes the JSON data to #verify_and_execute_recoup_refresh() to further 361 * check the details of the operation specified. If everything checks out, 362 * this will ultimately lead to the refund being executed, or rejected. 363 * 364 * @param connection the MHD connection to handle 365 * @param coin_pub public key of the coin 366 * @param root uploaded JSON data 367 * @return MHD result code 368 */ 369 MHD_RESULT 370 TEH_handler_recoup_refresh (struct MHD_Connection *connection, 371 const struct TALER_CoinSpendPublicKeyP *coin_pub, 372 const json_t *root) 373 { 374 enum GNUNET_GenericReturnValue ret; 375 struct TALER_CoinPublicInfo coin = {0}; 376 union GNUNET_CRYPTO_BlindingSecretP coin_bks; 377 struct TALER_CoinSpendSignatureP coin_sig; 378 struct TALER_ExchangeBlindingValues exchange_vals; 379 union GNUNET_CRYPTO_BlindSessionNonce nonce; 380 bool no_nonce; 381 struct GNUNET_JSON_Specification spec[] = { 382 GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", 383 &coin.denom_pub_hash), 384 TALER_JSON_spec_denom_sig ("denom_sig", 385 &coin.denom_sig), 386 TALER_JSON_spec_exchange_blinding_values ("ewv", 387 &exchange_vals), 388 GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret", 389 &coin_bks), 390 GNUNET_JSON_spec_fixed_auto ("coin_sig", 391 &coin_sig), 392 GNUNET_JSON_spec_mark_optional ( 393 GNUNET_JSON_spec_fixed_auto ("h_age_commitment", 394 &coin.h_age_commitment), 395 &coin.no_age_commitment), 396 GNUNET_JSON_spec_mark_optional ( 397 GNUNET_JSON_spec_fixed_auto ("nonce", 398 &nonce), 399 &no_nonce), 400 GNUNET_JSON_spec_end () 401 }; 402 403 memset (&coin, 404 0, 405 sizeof (coin)); 406 coin.coin_pub = *coin_pub; 407 ret = TALER_MHD_parse_json_data (connection, 408 root, 409 spec); 410 if (GNUNET_SYSERR == ret) 411 return MHD_NO; /* hard failure */ 412 if (GNUNET_NO == ret) 413 return MHD_YES; /* failure */ 414 { 415 MHD_RESULT res; 416 417 res = verify_and_execute_recoup_refresh (connection, 418 &coin, 419 &exchange_vals, 420 &coin_bks, 421 no_nonce 422 ? NULL 423 : &nonce, 424 &coin_sig); 425 GNUNET_JSON_parse_free (spec); 426 return res; 427 } 428 } 429 430 431 /* end of taler-exchange-httpd_recoup-refresh.c */