exchange_api_recoup.c (11401B)
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 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 General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file lib/exchange_api_recoup.c 19 * @brief Implementation of the /recoup request of the exchange's HTTP API 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include <jansson.h> 24 #include <microhttpd.h> /* just for HTTP status codes */ 25 #include <gnunet/gnunet_util_lib.h> 26 #include <gnunet/gnunet_json_lib.h> 27 #include <gnunet/gnunet_curl_lib.h> 28 #include "taler/taler_json_lib.h" 29 #include "taler/taler_exchange_service.h" 30 #include "exchange_api_common.h" 31 #include "exchange_api_handle.h" 32 #include "taler/taler_signatures.h" 33 #include "exchange_api_curl_defaults.h" 34 35 36 /** 37 * @brief A Recoup Handle 38 */ 39 struct TALER_EXCHANGE_RecoupHandle 40 { 41 42 /** 43 * The keys of the exchange this request handle will use 44 */ 45 struct TALER_EXCHANGE_Keys *keys; 46 47 /** 48 * The url for this request. 49 */ 50 char *url; 51 52 /** 53 * Context for #TEH_curl_easy_post(). Keeps the data that must 54 * persist for Curl to make the upload. 55 */ 56 struct TALER_CURL_PostContext ctx; 57 58 /** 59 * Denomination key of the coin. 60 */ 61 struct TALER_EXCHANGE_DenomPublicKey pk; 62 63 /** 64 * Our signature requesting the recoup. 65 */ 66 struct TALER_CoinSpendSignatureP coin_sig; 67 68 /** 69 * Handle for the request. 70 */ 71 struct GNUNET_CURL_Job *job; 72 73 /** 74 * Function to call with the result. 75 */ 76 TALER_EXCHANGE_RecoupResultCallback cb; 77 78 /** 79 * Closure for @a cb. 80 */ 81 void *cb_cls; 82 83 /** 84 * Public key of the coin we are trying to get paid back. 85 */ 86 struct TALER_CoinSpendPublicKeyP coin_pub; 87 88 }; 89 90 91 /** 92 * Parse a recoup response. If it is valid, call the callback. 93 * 94 * @param ph recoup handle 95 * @param json json reply with the signature 96 * @return #GNUNET_OK if the signature is valid and we called the callback; 97 * #GNUNET_SYSERR if not (callback must still be called) 98 */ 99 static enum GNUNET_GenericReturnValue 100 process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph, 101 const json_t *json) 102 { 103 struct TALER_EXCHANGE_RecoupResponse rr = { 104 .hr.reply = json, 105 .hr.http_status = MHD_HTTP_OK 106 }; 107 struct GNUNET_JSON_Specification spec_withdraw[] = { 108 GNUNET_JSON_spec_fixed_auto ("reserve_pub", 109 &rr.details.ok.reserve_pub), 110 GNUNET_JSON_spec_end () 111 }; 112 113 if (GNUNET_OK != 114 GNUNET_JSON_parse (json, 115 spec_withdraw, 116 NULL, NULL)) 117 { 118 GNUNET_break_op (0); 119 return GNUNET_SYSERR; 120 } 121 ph->cb (ph->cb_cls, 122 &rr); 123 return GNUNET_OK; 124 } 125 126 127 /** 128 * Function called when we're done processing the 129 * HTTP /recoup request. 130 * 131 * @param cls the `struct TALER_EXCHANGE_RecoupHandle` 132 * @param response_code HTTP response code, 0 on error 133 * @param response parsed JSON result, NULL on error 134 */ 135 static void 136 handle_recoup_finished (void *cls, 137 long response_code, 138 const void *response) 139 { 140 struct TALER_EXCHANGE_RecoupHandle *ph = cls; 141 const json_t *j = response; 142 struct TALER_EXCHANGE_RecoupResponse rr = { 143 .hr.reply = j, 144 .hr.http_status = (unsigned int) response_code 145 }; 146 147 ph->job = NULL; 148 switch (response_code) 149 { 150 case 0: 151 rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 152 break; 153 case MHD_HTTP_OK: 154 if (GNUNET_OK != 155 process_recoup_response (ph, 156 j)) 157 { 158 GNUNET_break_op (0); 159 rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 160 rr.hr.http_status = 0; 161 break; 162 } 163 TALER_EXCHANGE_recoup_cancel (ph); 164 return; 165 case MHD_HTTP_BAD_REQUEST: 166 /* This should never happen, either us or the exchange is buggy 167 (or API version conflict); just pass JSON reply to the application */ 168 rr.hr.ec = TALER_JSON_get_error_code (j); 169 rr.hr.hint = TALER_JSON_get_error_hint (j); 170 break; 171 case MHD_HTTP_CONFLICT: 172 { 173 struct TALER_Amount min_key; 174 175 rr.hr.ec = TALER_JSON_get_error_code (j); 176 rr.hr.hint = TALER_JSON_get_error_hint (j); 177 if (GNUNET_OK != 178 TALER_EXCHANGE_get_min_denomination_ (ph->keys, 179 &min_key)) 180 { 181 GNUNET_break (0); 182 rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 183 rr.hr.http_status = 0; 184 break; 185 } 186 break; 187 } 188 case MHD_HTTP_FORBIDDEN: 189 /* Nothing really to verify, exchange says one of the signatures is 190 invalid; as we checked them, this should never happen, we 191 should pass the JSON reply to the application */ 192 rr.hr.ec = TALER_JSON_get_error_code (j); 193 rr.hr.hint = TALER_JSON_get_error_hint (j); 194 break; 195 case MHD_HTTP_NOT_FOUND: 196 /* Nothing really to verify, this should never 197 happen, we should pass the JSON reply to the application */ 198 rr.hr.ec = TALER_JSON_get_error_code (j); 199 rr.hr.hint = TALER_JSON_get_error_hint (j); 200 break; 201 case MHD_HTTP_GONE: 202 /* Kind of normal: the money was already sent to the merchant 203 (it was too late for the refund). */ 204 rr.hr.ec = TALER_JSON_get_error_code (j); 205 rr.hr.hint = TALER_JSON_get_error_hint (j); 206 break; 207 case MHD_HTTP_INTERNAL_SERVER_ERROR: 208 /* Server had an internal issue; we should retry, but this API 209 leaves this to the application */ 210 rr.hr.ec = TALER_JSON_get_error_code (j); 211 rr.hr.hint = TALER_JSON_get_error_hint (j); 212 break; 213 default: 214 /* unexpected response code */ 215 rr.hr.ec = TALER_JSON_get_error_code (j); 216 rr.hr.hint = TALER_JSON_get_error_hint (j); 217 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 218 "Unexpected response code %u/%d for exchange recoup\n", 219 (unsigned int) response_code, 220 (int) rr.hr.ec); 221 GNUNET_break (0); 222 break; 223 } 224 ph->cb (ph->cb_cls, 225 &rr); 226 TALER_EXCHANGE_recoup_cancel (ph); 227 } 228 229 230 struct TALER_EXCHANGE_RecoupHandle * 231 TALER_EXCHANGE_recoup ( 232 struct GNUNET_CURL_Context *ctx, 233 const char *url, 234 struct TALER_EXCHANGE_Keys *keys, 235 const struct TALER_EXCHANGE_DenomPublicKey *pk, 236 const struct TALER_DenominationSignature *denom_sig, 237 const struct TALER_ExchangeBlindingValues *blinding_values, 238 const struct TALER_PlanchetMasterSecretP *ps, 239 const struct TALER_HashBlindedPlanchetsP *h_planchets, 240 TALER_EXCHANGE_RecoupResultCallback recoup_cb, 241 void *recoup_cb_cls) 242 { 243 struct TALER_EXCHANGE_RecoupHandle *ph; 244 struct TALER_DenominationHashP h_denom_pub; 245 json_t *recoup_obj; 246 CURL *eh; 247 char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; 248 struct TALER_CoinSpendPrivateKeyP coin_priv; 249 union GNUNET_CRYPTO_BlindingSecretP bks; 250 251 ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle); 252 TALER_planchet_setup_coin_priv (ps, 253 blinding_values, 254 &coin_priv); 255 TALER_planchet_blinding_secret_create (ps, 256 blinding_values, 257 &bks); 258 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, 259 &ph->coin_pub.eddsa_pub); 260 TALER_denom_pub_hash (&pk->key, 261 &h_denom_pub); 262 TALER_wallet_recoup_sign (&h_denom_pub, 263 &bks, 264 &coin_priv, 265 &ph->coin_sig); 266 recoup_obj = GNUNET_JSON_PACK ( 267 GNUNET_JSON_pack_data_auto ("denom_pub_hash", 268 &h_denom_pub), 269 TALER_JSON_pack_denom_sig ("denom_sig", 270 denom_sig), 271 TALER_JSON_pack_exchange_blinding_values ("ewv", 272 blinding_values), 273 GNUNET_JSON_pack_data_auto ("coin_sig", 274 &ph->coin_sig), 275 GNUNET_JSON_pack_data_auto ("h_planchets", 276 h_planchets), 277 GNUNET_JSON_pack_data_auto ("coin_blind_key_secret", 278 &bks)); 279 switch (denom_sig->unblinded_sig->cipher) 280 { 281 case GNUNET_CRYPTO_BSA_INVALID: 282 json_decref (recoup_obj); 283 GNUNET_break (0); 284 GNUNET_free (ph); 285 return NULL; 286 case GNUNET_CRYPTO_BSA_RSA: 287 break; 288 case GNUNET_CRYPTO_BSA_CS: 289 { 290 union GNUNET_CRYPTO_BlindSessionNonce nonce; 291 292 /* NOTE: this is not elegant, and as per the note in TALER_coin_ev_hash() 293 it is not strictly clear that the nonce is needed. Best case would be 294 to find a way to include it more 'naturally' somehow, for example with 295 the variant union version of bks! */ 296 TALER_cs_withdraw_nonce_derive (ps, 297 &nonce.cs_nonce); 298 GNUNET_assert ( 299 0 == 300 json_object_set_new (recoup_obj, 301 "nonce", 302 GNUNET_JSON_from_data_auto ( 303 &nonce))); 304 } 305 } 306 307 { 308 char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; 309 char *end; 310 311 end = GNUNET_STRINGS_data_to_string ( 312 &ph->coin_pub, 313 sizeof (struct TALER_CoinSpendPublicKeyP), 314 pub_str, 315 sizeof (pub_str)); 316 *end = '\0'; 317 GNUNET_snprintf (arg_str, 318 sizeof (arg_str), 319 "coins/%s/recoup", 320 pub_str); 321 } 322 323 ph->pk = *pk; 324 memset (&ph->pk.key, 325 0, 326 sizeof (ph->pk.key)); /* zero out, as lifetime cannot be warranted */ 327 ph->cb = recoup_cb; 328 ph->cb_cls = recoup_cb_cls; 329 ph->url = TALER_url_join (url, 330 arg_str, 331 NULL); 332 if (NULL == ph->url) 333 { 334 json_decref (recoup_obj); 335 GNUNET_free (ph); 336 return NULL; 337 } 338 eh = TALER_EXCHANGE_curl_easy_get_ (ph->url); 339 if ( (NULL == eh) || 340 (GNUNET_OK != 341 TALER_curl_easy_post (&ph->ctx, 342 eh, 343 recoup_obj)) ) 344 { 345 GNUNET_break (0); 346 if (NULL != eh) 347 curl_easy_cleanup (eh); 348 json_decref (recoup_obj); 349 GNUNET_free (ph->url); 350 GNUNET_free (ph); 351 return NULL; 352 } 353 json_decref (recoup_obj); 354 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 355 "URL for recoup: `%s'\n", 356 ph->url); 357 ph->keys = TALER_EXCHANGE_keys_incref (keys); 358 ph->job = GNUNET_CURL_job_add2 (ctx, 359 eh, 360 ph->ctx.headers, 361 &handle_recoup_finished, 362 ph); 363 return ph; 364 } 365 366 367 void 368 TALER_EXCHANGE_recoup_cancel (struct TALER_EXCHANGE_RecoupHandle *ph) 369 { 370 if (NULL != ph->job) 371 { 372 GNUNET_CURL_job_cancel (ph->job); 373 ph->job = NULL; 374 } 375 GNUNET_free (ph->url); 376 TALER_curl_easy_post_finished (&ph->ctx); 377 TALER_EXCHANGE_keys_decref (ph->keys); 378 GNUNET_free (ph); 379 } 380 381 382 /* end of exchange_api_recoup.c */