exchange_api_post-recoup-withdraw.c (12047B)
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 <jansson.h> 23 #include <microhttpd.h> /* just for HTTP status codes */ 24 #include <gnunet/gnunet_util_lib.h> 25 #include <gnunet/gnunet_json_lib.h> 26 #include <gnunet/gnunet_curl_lib.h> 27 #include "taler/taler_json_lib.h" 28 #include "exchange_api_common.h" 29 #include "exchange_api_handle.h" 30 #include "taler/taler_signatures.h" 31 #include "exchange_api_curl_defaults.h" 32 33 34 /** 35 * @brief A Recoup Handle 36 */ 37 struct TALER_EXCHANGE_PostRecoupWithdrawHandle 38 { 39 40 /** 41 * The base URL for this request. 42 */ 43 char *base_url; 44 45 /** 46 * The full URL for this request, set during _start. 47 */ 48 char *url; 49 50 /** 51 * Minor context that holds body and headers. 52 */ 53 struct TALER_CURL_PostContext post_ctx; 54 55 /** 56 * Handle for the request. 57 */ 58 struct GNUNET_CURL_Job *job; 59 60 /** 61 * Function to call with the result. 62 */ 63 TALER_EXCHANGE_PostRecoupWithdrawCallback cb; 64 65 /** 66 * Closure for @a cb. 67 */ 68 TALER_EXCHANGE_POST_RECOUP_WITHDRAW_RESULT_CLOSURE *cb_cls; 69 70 /** 71 * Reference to the execution context. 72 */ 73 struct GNUNET_CURL_Context *ctx; 74 75 /** 76 * The keys of the exchange this request handle will use. 77 */ 78 struct TALER_EXCHANGE_Keys *keys; 79 80 /** 81 * Public key of the coin we are trying to get paid back. 82 */ 83 struct TALER_CoinSpendPublicKeyP coin_pub; 84 85 /** 86 * Pre-built request body. 87 */ 88 json_t *body; 89 90 }; 91 92 93 /** 94 * Parse a recoup response. If it is valid, call the callback. 95 * 96 * @param ph recoup handle 97 * @param json json reply with the signature 98 * @return #GNUNET_OK if the signature is valid and we called the callback; 99 * #GNUNET_SYSERR if not (callback must still be called) 100 */ 101 static enum GNUNET_GenericReturnValue 102 process_recoup_response ( 103 const struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph, 104 const json_t *json) 105 { 106 struct TALER_EXCHANGE_PostRecoupWithdrawResponse rr = { 107 .hr.reply = json, 108 .hr.http_status = MHD_HTTP_OK 109 }; 110 struct GNUNET_JSON_Specification spec_withdraw[] = { 111 GNUNET_JSON_spec_fixed_auto ("reserve_pub", 112 &rr.details.ok.reserve_pub), 113 GNUNET_JSON_spec_end () 114 }; 115 116 if (GNUNET_OK != 117 GNUNET_JSON_parse (json, 118 spec_withdraw, 119 NULL, NULL)) 120 { 121 GNUNET_break_op (0); 122 return GNUNET_SYSERR; 123 } 124 ph->cb (ph->cb_cls, 125 &rr); 126 return GNUNET_OK; 127 } 128 129 130 /** 131 * Function called when we're done processing the 132 * HTTP /recoup request. 133 * 134 * @param cls the `struct TALER_EXCHANGE_PostRecoupWithdrawHandle` 135 * @param response_code HTTP response code, 0 on error 136 * @param response parsed JSON result, NULL on error 137 */ 138 static void 139 handle_recoup_finished (void *cls, 140 long response_code, 141 const void *response) 142 { 143 struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph = cls; 144 const json_t *j = response; 145 struct TALER_EXCHANGE_PostRecoupWithdrawResponse rr = { 146 .hr.reply = j, 147 .hr.http_status = (unsigned int) response_code 148 }; 149 150 ph->job = NULL; 151 switch (response_code) 152 { 153 case 0: 154 rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 155 break; 156 case MHD_HTTP_OK: 157 if (GNUNET_OK != 158 process_recoup_response (ph, 159 j)) 160 { 161 GNUNET_break_op (0); 162 rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 163 rr.hr.http_status = 0; 164 break; 165 } 166 TALER_EXCHANGE_post_recoup_withdraw_cancel (ph); 167 return; 168 case MHD_HTTP_BAD_REQUEST: 169 /* This should never happen, either us or the exchange is buggy 170 (or API version conflict); just pass JSON reply to the application */ 171 rr.hr.ec = TALER_JSON_get_error_code (j); 172 rr.hr.hint = TALER_JSON_get_error_hint (j); 173 break; 174 case MHD_HTTP_CONFLICT: 175 { 176 struct TALER_Amount min_key; 177 178 rr.hr.ec = TALER_JSON_get_error_code (j); 179 rr.hr.hint = TALER_JSON_get_error_hint (j); 180 if (GNUNET_OK != 181 TALER_EXCHANGE_get_min_denomination_ (ph->keys, 182 &min_key)) 183 { 184 GNUNET_break (0); 185 rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 186 rr.hr.http_status = 0; 187 break; 188 } 189 break; 190 } 191 case MHD_HTTP_FORBIDDEN: 192 /* Nothing really to verify, exchange says one of the signatures is 193 invalid; as we checked them, this should never happen, we 194 should pass the JSON reply to the application */ 195 rr.hr.ec = TALER_JSON_get_error_code (j); 196 rr.hr.hint = TALER_JSON_get_error_hint (j); 197 break; 198 case MHD_HTTP_NOT_FOUND: 199 /* Nothing really to verify, this should never 200 happen, we should pass the JSON reply to the application */ 201 rr.hr.ec = TALER_JSON_get_error_code (j); 202 rr.hr.hint = TALER_JSON_get_error_hint (j); 203 break; 204 case MHD_HTTP_GONE: 205 /* Kind of normal: the money was already sent to the merchant 206 (it was too late for the refund). */ 207 rr.hr.ec = TALER_JSON_get_error_code (j); 208 rr.hr.hint = TALER_JSON_get_error_hint (j); 209 break; 210 case MHD_HTTP_INTERNAL_SERVER_ERROR: 211 /* Server had an internal issue; we should retry, but this API 212 leaves this to the application */ 213 rr.hr.ec = TALER_JSON_get_error_code (j); 214 rr.hr.hint = TALER_JSON_get_error_hint (j); 215 break; 216 default: 217 /* unexpected response code */ 218 rr.hr.ec = TALER_JSON_get_error_code (j); 219 rr.hr.hint = TALER_JSON_get_error_hint (j); 220 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 221 "Unexpected response code %u/%d for exchange recoup\n", 222 (unsigned int) response_code, 223 (int) rr.hr.ec); 224 GNUNET_break (0); 225 break; 226 } 227 ph->cb (ph->cb_cls, 228 &rr); 229 TALER_EXCHANGE_post_recoup_withdraw_cancel (ph); 230 } 231 232 233 struct TALER_EXCHANGE_PostRecoupWithdrawHandle * 234 TALER_EXCHANGE_post_recoup_withdraw_create ( 235 struct GNUNET_CURL_Context *ctx, 236 const char *url, 237 struct TALER_EXCHANGE_Keys *keys, 238 const struct TALER_EXCHANGE_DenomPublicKey *pk, 239 const struct TALER_DenominationSignature *denom_sig, 240 const struct TALER_ExchangeBlindingValues *exchange_vals, 241 const struct TALER_BlindingMasterSeedP *blinding_seed, 242 uint32_t coin_offset, 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 const uint32_t indices[] = { coin_offset }; 298 299 if (NULL == blinding_seed) 300 { 301 json_decref (ph->body); 302 GNUNET_free (ph->base_url); 303 TALER_EXCHANGE_keys_decref (ph->keys); 304 GNUNET_free (ph); 305 GNUNET_break (0); 306 return NULL; 307 } 308 TALER_cs_derive_only_cs_blind_nonces_from_seed ( 309 blinding_seed, 310 false, 311 1, 312 indices, 313 &nonce); 314 GNUNET_assert ( 315 0 == 316 json_object_set_new (ph->body, 317 "nonce", 318 GNUNET_JSON_from_data_auto ( 319 &nonce))); 320 } 321 break; 322 } 323 return ph; 324 } 325 326 327 enum TALER_ErrorCode 328 TALER_EXCHANGE_post_recoup_withdraw_start ( 329 struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph, 330 TALER_EXCHANGE_PostRecoupWithdrawCallback cb, 331 TALER_EXCHANGE_POST_RECOUP_WITHDRAW_RESULT_CLOSURE *cb_cls) 332 { 333 CURL *eh; 334 char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; 335 336 ph->cb = cb; 337 ph->cb_cls = cb_cls; 338 { 339 char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; 340 char *end; 341 342 end = GNUNET_STRINGS_data_to_string ( 343 &ph->coin_pub, 344 sizeof (struct TALER_CoinSpendPublicKeyP), 345 pub_str, 346 sizeof (pub_str)); 347 *end = '\0'; 348 GNUNET_snprintf (arg_str, 349 sizeof (arg_str), 350 "coins/%s/recoup", 351 pub_str); 352 } 353 ph->url = TALER_url_join (ph->base_url, 354 arg_str, 355 NULL); 356 if (NULL == ph->url) 357 { 358 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 359 "Could not construct request URL.\n"); 360 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 361 } 362 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 363 "URL for recoup: `%s'\n", 364 ph->url); 365 eh = TALER_EXCHANGE_curl_easy_get_ (ph->url); 366 if ( (NULL == eh) || 367 (GNUNET_OK != 368 TALER_curl_easy_post (&ph->post_ctx, 369 eh, 370 ph->body)) ) 371 { 372 GNUNET_break (0); 373 if (NULL != eh) 374 curl_easy_cleanup (eh); 375 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 376 } 377 ph->job = GNUNET_CURL_job_add2 (ph->ctx, 378 eh, 379 ph->post_ctx.headers, 380 &handle_recoup_finished, 381 ph); 382 if (NULL == ph->job) 383 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 384 return TALER_EC_NONE; 385 } 386 387 388 void 389 TALER_EXCHANGE_post_recoup_withdraw_cancel ( 390 struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph) 391 { 392 if (NULL != ph->job) 393 { 394 GNUNET_CURL_job_cancel (ph->job); 395 ph->job = NULL; 396 } 397 TALER_curl_easy_post_finished (&ph->post_ctx); 398 GNUNET_free (ph->url); 399 GNUNET_free (ph->base_url); 400 json_decref (ph->body); 401 TALER_EXCHANGE_keys_decref (ph->keys); 402 GNUNET_free (ph); 403 } 404 405 406 /* end of exchange_api_post-recoup-withdraw.c */