exchange_api_recoup_refresh.c (11329B)
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_refresh.c 19 * @brief Implementation of the /recoup-refresh 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_RecoupRefreshHandle 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 * Handle for the request. 65 */ 66 struct GNUNET_CURL_Job *job; 67 68 /** 69 * Function to call with the result. 70 */ 71 TALER_EXCHANGE_RecoupRefreshResultCallback cb; 72 73 /** 74 * Closure for @a cb. 75 */ 76 void *cb_cls; 77 78 /** 79 * Public key of the coin we are trying to get paid back. 80 */ 81 struct TALER_CoinSpendPublicKeyP coin_pub; 82 83 /** 84 * Signature affirming the recoup-refresh operation. 85 */ 86 struct TALER_CoinSpendSignatureP coin_sig; 87 88 }; 89 90 91 /** 92 * Parse a recoup-refresh 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 ( 101 const struct TALER_EXCHANGE_RecoupRefreshHandle *ph, 102 const json_t *json) 103 { 104 struct TALER_EXCHANGE_RecoupRefreshResponse rrr = { 105 .hr.reply = json, 106 .hr.http_status = MHD_HTTP_OK 107 }; 108 struct GNUNET_JSON_Specification spec_refresh[] = { 109 GNUNET_JSON_spec_fixed_auto ("old_coin_pub", 110 &rrr.details.ok.old_coin_pub), 111 GNUNET_JSON_spec_end () 112 }; 113 114 if (GNUNET_OK != 115 GNUNET_JSON_parse (json, 116 spec_refresh, 117 NULL, NULL)) 118 { 119 GNUNET_break_op (0); 120 return GNUNET_SYSERR; 121 } 122 ph->cb (ph->cb_cls, 123 &rrr); 124 return GNUNET_OK; 125 } 126 127 128 /** 129 * Function called when we're done processing the 130 * HTTP /recoup-refresh request. 131 * 132 * @param cls the `struct TALER_EXCHANGE_RecoupRefreshHandle` 133 * @param response_code HTTP response code, 0 on error 134 * @param response parsed JSON result, NULL on error 135 */ 136 static void 137 handle_recoup_refresh_finished (void *cls, 138 long response_code, 139 const void *response) 140 { 141 struct TALER_EXCHANGE_RecoupRefreshHandle *ph = cls; 142 const json_t *j = response; 143 struct TALER_EXCHANGE_RecoupRefreshResponse rrr = { 144 .hr.reply = j, 145 .hr.http_status = (unsigned int) response_code 146 }; 147 148 ph->job = NULL; 149 switch (response_code) 150 { 151 case 0: 152 rrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 153 break; 154 case MHD_HTTP_OK: 155 if (GNUNET_OK != 156 process_recoup_response (ph, 157 j)) 158 { 159 GNUNET_break_op (0); 160 rrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 161 rrr.hr.http_status = 0; 162 break; 163 } 164 TALER_EXCHANGE_recoup_refresh_cancel (ph); 165 return; 166 case MHD_HTTP_BAD_REQUEST: 167 /* This should never happen, either us or the exchange is buggy 168 (or API version conflict); just pass JSON reply to the application */ 169 rrr.hr.ec = TALER_JSON_get_error_code (j); 170 rrr.hr.hint = TALER_JSON_get_error_hint (j); 171 break; 172 case MHD_HTTP_FORBIDDEN: 173 /* Nothing really to verify, exchange says one of the signatures is 174 invalid; as we checked them, this should never happen, we 175 should pass the JSON reply to the application */ 176 rrr.hr.ec = TALER_JSON_get_error_code (j); 177 rrr.hr.hint = TALER_JSON_get_error_hint (j); 178 break; 179 case MHD_HTTP_NOT_FOUND: 180 /* Nothing really to verify, this should never 181 happen, we should pass the JSON reply to the application */ 182 rrr.hr.ec = TALER_JSON_get_error_code (j); 183 rrr.hr.hint = TALER_JSON_get_error_hint (j); 184 break; 185 case MHD_HTTP_CONFLICT: 186 rrr.hr.ec = TALER_JSON_get_error_code (j); 187 rrr.hr.hint = TALER_JSON_get_error_hint (j); 188 break; 189 case MHD_HTTP_GONE: 190 /* Kind of normal: the money was already sent to the merchant 191 (it was too late for the refund). */ 192 rrr.hr.ec = TALER_JSON_get_error_code (j); 193 rrr.hr.hint = TALER_JSON_get_error_hint (j); 194 break; 195 case MHD_HTTP_INTERNAL_SERVER_ERROR: 196 /* Server had an internal issue; we should retry, but this API 197 leaves this to the application */ 198 rrr.hr.ec = TALER_JSON_get_error_code (j); 199 rrr.hr.hint = TALER_JSON_get_error_hint (j); 200 break; 201 default: 202 /* unexpected response code */ 203 rrr.hr.ec = TALER_JSON_get_error_code (j); 204 rrr.hr.hint = TALER_JSON_get_error_hint (j); 205 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 206 "Unexpected response code %u/%d for exchange recoup\n", 207 (unsigned int) response_code, 208 (int) rrr.hr.ec); 209 GNUNET_break (0); 210 break; 211 } 212 ph->cb (ph->cb_cls, 213 &rrr); 214 TALER_EXCHANGE_recoup_refresh_cancel (ph); 215 } 216 217 218 struct TALER_EXCHANGE_RecoupRefreshHandle * 219 TALER_EXCHANGE_recoup_refresh ( 220 struct GNUNET_CURL_Context *ctx, 221 const char *url, 222 struct TALER_EXCHANGE_Keys *keys, 223 const struct TALER_EXCHANGE_DenomPublicKey *pk, 224 const struct TALER_DenominationSignature *denom_sig, 225 const struct TALER_ExchangeBlindingValues *blinding_values, 226 const struct TALER_RefreshMasterSecretP *rms, 227 const struct TALER_PlanchetMasterSecretP *ps, 228 unsigned int idx, 229 TALER_EXCHANGE_RecoupRefreshResultCallback recoup_cb, 230 void *recoup_cb_cls) 231 { 232 struct TALER_EXCHANGE_RecoupRefreshHandle *ph; 233 struct TALER_DenominationHashP h_denom_pub; 234 json_t *recoup_obj; 235 CURL *eh; 236 char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; 237 struct TALER_CoinSpendPrivateKeyP coin_priv; 238 union GNUNET_CRYPTO_BlindingSecretP bks; 239 240 GNUNET_assert (NULL != recoup_cb); 241 ph = GNUNET_new (struct TALER_EXCHANGE_RecoupRefreshHandle); 242 ph->pk = *pk; 243 memset (&ph->pk.key, 244 0, 245 sizeof (ph->pk.key)); /* zero out, as lifetime cannot be warranted */ 246 ph->cb = recoup_cb; 247 ph->cb_cls = recoup_cb_cls; 248 TALER_planchet_setup_coin_priv (ps, 249 blinding_values, 250 &coin_priv); 251 TALER_planchet_blinding_secret_create (ps, 252 blinding_values, 253 &bks); 254 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, 255 &ph->coin_pub.eddsa_pub); 256 TALER_denom_pub_hash (&pk->key, 257 &h_denom_pub); 258 TALER_wallet_recoup_refresh_sign (&h_denom_pub, 259 &bks, 260 &coin_priv, 261 &ph->coin_sig); 262 recoup_obj = GNUNET_JSON_PACK ( 263 GNUNET_JSON_pack_data_auto ("denom_pub_hash", 264 &h_denom_pub), 265 TALER_JSON_pack_denom_sig ("denom_sig", 266 denom_sig), 267 TALER_JSON_pack_exchange_blinding_values ("ewv", 268 blinding_values), 269 GNUNET_JSON_pack_data_auto ("coin_sig", 270 &ph->coin_sig), 271 GNUNET_JSON_pack_data_auto ("coin_blind_key_secret", 272 &bks)); 273 274 switch (denom_sig->unblinded_sig->cipher) 275 { 276 case GNUNET_CRYPTO_BSA_INVALID: 277 json_decref (recoup_obj); 278 GNUNET_break (0); 279 GNUNET_free (ph); 280 return NULL; 281 case GNUNET_CRYPTO_BSA_RSA: 282 break; 283 case GNUNET_CRYPTO_BSA_CS: 284 { 285 union GNUNET_CRYPTO_BlindSessionNonce nonce; 286 287 /* NOTE: this is not elegant, and as per the note in TALER_coin_ev_hash() 288 it is not strictly clear that the nonce is needed. Best case would be 289 to find a way to include it more 'naturally' somehow, for example with 290 the variant union version of bks! */ 291 TALER_cs_refresh_nonce_derive (rms, 292 idx, 293 &nonce.cs_nonce); 294 GNUNET_assert ( 295 0 == 296 json_object_set_new (recoup_obj, 297 "nonce", 298 GNUNET_JSON_from_data_auto ( 299 &nonce))); 300 } 301 break; 302 } 303 304 { 305 char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; 306 char *end; 307 308 end = GNUNET_STRINGS_data_to_string ( 309 &ph->coin_pub, 310 sizeof (struct TALER_CoinSpendPublicKeyP), 311 pub_str, 312 sizeof (pub_str)); 313 *end = '\0'; 314 GNUNET_snprintf (arg_str, 315 sizeof (arg_str), 316 "coins/%s/recoup-refresh", 317 pub_str); 318 } 319 320 ph->url = TALER_url_join (url, 321 arg_str, 322 NULL); 323 if (NULL == ph->url) 324 { 325 json_decref (recoup_obj); 326 GNUNET_free (ph); 327 return NULL; 328 } 329 eh = TALER_EXCHANGE_curl_easy_get_ (ph->url); 330 if ( (NULL == eh) || 331 (GNUNET_OK != 332 TALER_curl_easy_post (&ph->ctx, 333 eh, 334 recoup_obj)) ) 335 { 336 GNUNET_break (0); 337 if (NULL != eh) 338 curl_easy_cleanup (eh); 339 json_decref (recoup_obj); 340 GNUNET_free (ph->url); 341 GNUNET_free (ph); 342 return NULL; 343 } 344 json_decref (recoup_obj); 345 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 346 "URL for recoup-refresh: `%s'\n", 347 ph->url); 348 ph->keys = TALER_EXCHANGE_keys_incref (keys); 349 ph->job = GNUNET_CURL_job_add2 (ctx, 350 eh, 351 ph->ctx.headers, 352 &handle_recoup_refresh_finished, 353 ph); 354 return ph; 355 } 356 357 358 void 359 TALER_EXCHANGE_recoup_refresh_cancel ( 360 struct TALER_EXCHANGE_RecoupRefreshHandle *ph) 361 { 362 if (NULL != ph->job) 363 { 364 GNUNET_CURL_job_cancel (ph->job); 365 ph->job = NULL; 366 } 367 GNUNET_free (ph->url); 368 TALER_curl_easy_post_finished (&ph->ctx); 369 TALER_EXCHANGE_keys_decref (ph->keys); 370 GNUNET_free (ph); 371 } 372 373 374 /* end of exchange_api_recoup_refresh.c */