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