Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2015-2022 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_melt.c
19 : * @brief Implementation of the /coins/$COIN_PUB/melt request
20 : * @author Christian Grothoff
21 : */
22 : #include "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_json_lib.h"
29 : #include "taler_exchange_service.h"
30 : #include "exchange_api_common.h"
31 : #include "exchange_api_handle.h"
32 : #include "taler_signatures.h"
33 : #include "exchange_api_curl_defaults.h"
34 : #include "exchange_api_refresh_common.h"
35 :
36 :
37 : /**
38 : * @brief A /coins/$COIN_PUB/melt Handle
39 : */
40 : struct TALER_EXCHANGE_MeltHandle
41 : {
42 :
43 : /**
44 : * The connection to exchange this request handle will use
45 : */
46 : struct TALER_EXCHANGE_Handle *exchange;
47 :
48 : /**
49 : * The url for this request.
50 : */
51 : char *url;
52 :
53 : /**
54 : * Context for #TEH_curl_easy_post(). Keeps the data that must
55 : * persist for Curl to make the upload.
56 : */
57 : struct TALER_CURL_PostContext ctx;
58 :
59 : /**
60 : * Handle for the request.
61 : */
62 : struct GNUNET_CURL_Job *job;
63 :
64 : /**
65 : * Function to call with refresh melt failure results.
66 : */
67 : TALER_EXCHANGE_MeltCallback melt_cb;
68 :
69 : /**
70 : * Closure for @e result_cb and @e melt_failure_cb.
71 : */
72 : void *melt_cb_cls;
73 :
74 : /**
75 : * Actual information about the melt operation.
76 : */
77 : struct MeltData md;
78 :
79 : /**
80 : * The secret the entire melt operation is seeded from.
81 : */
82 : struct TALER_RefreshMasterSecretP rms;
83 :
84 : /**
85 : * Details about the characteristics of the requested melt operation.
86 : */
87 : const struct TALER_EXCHANGE_RefreshData *rd;
88 :
89 : /**
90 : * Array of `num_fresh_coins` per-coin values
91 : * returned from melt operation.
92 : */
93 : struct TALER_EXCHANGE_MeltBlindingDetail *mbds;
94 :
95 : /**
96 : * Handle for the preflight request, or NULL.
97 : */
98 : struct TALER_EXCHANGE_CsRMeltHandle *csr;
99 :
100 : /**
101 : * Public key of the coin being melted.
102 : */
103 : struct TALER_CoinSpendPublicKeyP coin_pub;
104 :
105 : /**
106 : * Signature affirming the melt.
107 : */
108 : struct TALER_CoinSpendSignatureP coin_sig;
109 :
110 : /**
111 : * @brief Public information about the coin's denomination key
112 : */
113 : const struct TALER_EXCHANGE_DenomPublicKey *dki;
114 :
115 : /**
116 : * Gamma value chosen by the exchange during melt.
117 : */
118 : uint32_t noreveal_index;
119 :
120 : /**
121 : * True if we need to include @e rms in our melt request.
122 : */
123 : bool send_rms;
124 : };
125 :
126 :
127 : /**
128 : * Verify that the signature on the "200 OK" response
129 : * from the exchange is valid.
130 : *
131 : * @param[in,out] mh melt handle
132 : * @param json json reply with the signature
133 : * @param[out] exchange_pub public key of the exchange used for the signature
134 : * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
135 : */
136 : static enum GNUNET_GenericReturnValue
137 0 : verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
138 : const json_t *json,
139 : struct TALER_ExchangePublicKeyP *exchange_pub)
140 : {
141 : struct TALER_ExchangeSignatureP exchange_sig;
142 : const struct TALER_EXCHANGE_Keys *key_state;
143 : struct GNUNET_JSON_Specification spec[] = {
144 0 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
145 : &exchange_sig),
146 0 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
147 : exchange_pub),
148 0 : GNUNET_JSON_spec_uint32 ("noreveal_index",
149 : &mh->noreveal_index),
150 0 : GNUNET_JSON_spec_end ()
151 : };
152 :
153 0 : if (GNUNET_OK !=
154 0 : GNUNET_JSON_parse (json,
155 : spec,
156 : NULL, NULL))
157 : {
158 0 : GNUNET_break_op (0);
159 0 : return GNUNET_SYSERR;
160 : }
161 : /* check that exchange signing key is permitted */
162 0 : key_state = TALER_EXCHANGE_get_keys (mh->exchange);
163 0 : if (GNUNET_OK !=
164 0 : TALER_EXCHANGE_test_signing_key (key_state,
165 : exchange_pub))
166 : {
167 0 : GNUNET_break_op (0);
168 0 : return GNUNET_SYSERR;
169 : }
170 :
171 : /* check that noreveal index is in permitted range */
172 0 : if (TALER_CNC_KAPPA <= mh->noreveal_index)
173 : {
174 0 : GNUNET_break_op (0);
175 0 : return GNUNET_SYSERR;
176 : }
177 :
178 0 : if (GNUNET_OK !=
179 0 : TALER_exchange_online_melt_confirmation_verify (
180 0 : &mh->md.rc,
181 : mh->noreveal_index,
182 : exchange_pub,
183 : &exchange_sig))
184 : {
185 0 : GNUNET_break_op (0);
186 0 : return GNUNET_SYSERR;
187 : }
188 0 : return GNUNET_OK;
189 : }
190 :
191 :
192 : /**
193 : * Function called when we're done processing the
194 : * HTTP /coins/$COIN_PUB/melt request.
195 : *
196 : * @param cls the `struct TALER_EXCHANGE_MeltHandle`
197 : * @param response_code HTTP response code, 0 on error
198 : * @param response parsed JSON result, NULL on error
199 : */
200 : static void
201 0 : handle_melt_finished (void *cls,
202 : long response_code,
203 : const void *response)
204 : {
205 0 : struct TALER_EXCHANGE_MeltHandle *mh = cls;
206 0 : const json_t *j = response;
207 0 : struct TALER_EXCHANGE_MeltResponse mr = {
208 : .hr.reply = j,
209 0 : .hr.http_status = (unsigned int) response_code
210 : };
211 : const struct TALER_EXCHANGE_Keys *keys;
212 :
213 0 : mh->job = NULL;
214 0 : keys = TALER_EXCHANGE_get_keys (mh->exchange);
215 0 : switch (response_code)
216 : {
217 0 : case 0:
218 0 : mr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
219 0 : break;
220 0 : case MHD_HTTP_OK:
221 0 : if (GNUNET_OK !=
222 0 : verify_melt_signature_ok (mh,
223 : j,
224 : &mr.details.success.sign_key))
225 : {
226 0 : GNUNET_break_op (0);
227 0 : mr.hr.http_status = 0;
228 0 : mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
229 0 : break;
230 : }
231 0 : mr.details.success.noreveal_index = mh->noreveal_index;
232 0 : mr.details.success.num_mbds = mh->rd->fresh_pks_len;
233 0 : mr.details.success.mbds = mh->mbds;
234 0 : mh->melt_cb (mh->melt_cb_cls,
235 : &mr);
236 0 : mh->melt_cb = NULL;
237 0 : break;
238 0 : case MHD_HTTP_BAD_REQUEST:
239 : /* This should never happen, either us or the exchange is buggy
240 : (or API version conflict); just pass JSON reply to the application */
241 0 : mr.hr.ec = TALER_JSON_get_error_code (j);
242 0 : mr.hr.hint = TALER_JSON_get_error_hint (j);
243 0 : break;
244 0 : case MHD_HTTP_CONFLICT:
245 0 : mr.hr.ec = TALER_JSON_get_error_code (j);
246 0 : mr.hr.hint = TALER_JSON_get_error_hint (j);
247 0 : if (GNUNET_OK !=
248 0 : TALER_EXCHANGE_check_coin_conflict_ (
249 : keys,
250 : j,
251 : mh->dki,
252 0 : &mh->coin_pub,
253 0 : &mh->coin_sig,
254 0 : &mh->md.melted_coin.melt_amount_with_fee))
255 : {
256 0 : GNUNET_break_op (0);
257 0 : mr.hr.http_status = 0;
258 0 : mr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
259 0 : break;
260 : }
261 0 : break;
262 0 : case MHD_HTTP_FORBIDDEN:
263 : /* Nothing really to verify, exchange says one of the signatures is
264 : invalid; assuming we checked them, this should never happen, we
265 : should pass the JSON reply to the application */
266 0 : mr.hr.ec = TALER_JSON_get_error_code (j);
267 0 : mr.hr.hint = TALER_JSON_get_error_hint (j);
268 0 : break;
269 0 : case MHD_HTTP_NOT_FOUND:
270 : /* Nothing really to verify, this should never
271 : happen, we should pass the JSON reply to the application */
272 0 : mr.hr.ec = TALER_JSON_get_error_code (j);
273 0 : mr.hr.hint = TALER_JSON_get_error_hint (j);
274 0 : break;
275 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
276 : /* Server had an internal issue; we should retry, but this API
277 : leaves this to the application */
278 0 : mr.hr.ec = TALER_JSON_get_error_code (j);
279 0 : mr.hr.hint = TALER_JSON_get_error_hint (j);
280 0 : break;
281 0 : default:
282 : /* unexpected response code */
283 0 : mr.hr.ec = TALER_JSON_get_error_code (j);
284 0 : mr.hr.hint = TALER_JSON_get_error_hint (j);
285 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
286 : "Unexpected response code %u/%d for exchange melt\n",
287 : (unsigned int) response_code,
288 : mr.hr.ec);
289 0 : GNUNET_break_op (0);
290 0 : break;
291 : }
292 0 : if (NULL != mh->melt_cb)
293 0 : mh->melt_cb (mh->melt_cb_cls,
294 : &mr);
295 0 : TALER_EXCHANGE_melt_cancel (mh);
296 0 : }
297 :
298 :
299 : /**
300 : * Start the actual melt operation, now that we have
301 : * the exchange's input values.
302 : *
303 : * @param[in,out] mh melt operation to run
304 : * @return #GNUNET_OK if we could start the operation
305 : */
306 : static enum GNUNET_GenericReturnValue
307 0 : start_melt (struct TALER_EXCHANGE_MeltHandle *mh)
308 0 : {
309 : const struct TALER_EXCHANGE_Keys *key_state;
310 : json_t *melt_obj;
311 : CURL *eh;
312 : struct GNUNET_CURL_Context *ctx;
313 : char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
314 : struct TALER_DenominationHashP h_denom_pub;
315 0 : struct TALER_ExchangeWithdrawValues alg_values[mh->rd->fresh_pks_len];
316 :
317 0 : for (unsigned int i = 0; i<mh->rd->fresh_pks_len; i++)
318 0 : alg_values[i] = mh->mbds[i].alg_value;
319 0 : if (GNUNET_OK !=
320 0 : TALER_EXCHANGE_get_melt_data_ (&mh->rms,
321 : mh->rd,
322 : alg_values,
323 : &mh->md))
324 : {
325 0 : GNUNET_break (0);
326 0 : return GNUNET_SYSERR;
327 : }
328 0 : TALER_denom_pub_hash (&mh->md.melted_coin.pub_key,
329 : &h_denom_pub);
330 0 : TALER_wallet_melt_sign (&mh->md.melted_coin.melt_amount_with_fee,
331 0 : &mh->md.melted_coin.fee_melt,
332 0 : &mh->md.rc,
333 : &h_denom_pub,
334 : mh->md.melted_coin.h_age_commitment,
335 0 : &mh->md.melted_coin.coin_priv,
336 : &mh->coin_sig);
337 0 : GNUNET_CRYPTO_eddsa_key_get_public (&mh->md.melted_coin.coin_priv.eddsa_priv,
338 : &mh->coin_pub.eddsa_pub);
339 0 : melt_obj = GNUNET_JSON_PACK (
340 : GNUNET_JSON_pack_data_auto ("denom_pub_hash",
341 : &h_denom_pub),
342 : TALER_JSON_pack_denom_sig ("denom_sig",
343 : &mh->md.melted_coin.sig),
344 : GNUNET_JSON_pack_data_auto ("confirm_sig",
345 : &mh->coin_sig),
346 : TALER_JSON_pack_amount ("value_with_fee",
347 : &mh->md.melted_coin.melt_amount_with_fee),
348 : GNUNET_JSON_pack_data_auto ("rc",
349 : &mh->md.rc),
350 : GNUNET_JSON_pack_allow_null (
351 : mh->md.melted_coin.h_age_commitment
352 : ? GNUNET_JSON_pack_data_auto ("age_commitment_hash",
353 : mh->md.melted_coin.h_age_commitment)
354 : : GNUNET_JSON_pack_string ("age_commitment_hash",
355 : NULL)),
356 : GNUNET_JSON_pack_allow_null (
357 : mh->send_rms
358 : ? GNUNET_JSON_pack_data_auto ("rms",
359 : &mh->rms)
360 : : GNUNET_JSON_pack_string ("rms",
361 : NULL)));
362 : {
363 : char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
364 : char *end;
365 :
366 0 : end = GNUNET_STRINGS_data_to_string (
367 0 : &mh->coin_pub,
368 : sizeof (struct TALER_CoinSpendPublicKeyP),
369 : pub_str,
370 : sizeof (pub_str));
371 0 : *end = '\0';
372 0 : GNUNET_snprintf (arg_str,
373 : sizeof (arg_str),
374 : "/coins/%s/melt",
375 : pub_str);
376 : }
377 :
378 0 : ctx = TEAH_handle_to_context (mh->exchange);
379 0 : key_state = TALER_EXCHANGE_get_keys (mh->exchange);
380 0 : mh->dki = TALER_EXCHANGE_get_denomination_key (key_state,
381 0 : &mh->md.melted_coin.pub_key);
382 :
383 : /* and now we can at last begin the actual request handling */
384 :
385 0 : mh->url = TEAH_path_to_url (mh->exchange,
386 : arg_str);
387 0 : if (NULL == mh->url)
388 : {
389 0 : json_decref (melt_obj);
390 0 : return GNUNET_SYSERR;
391 : }
392 0 : eh = TALER_EXCHANGE_curl_easy_get_ (mh->url);
393 0 : if ( (NULL == eh) ||
394 : (GNUNET_OK !=
395 0 : TALER_curl_easy_post (&mh->ctx,
396 : eh,
397 : melt_obj)) )
398 : {
399 0 : GNUNET_break (0);
400 0 : if (NULL != eh)
401 0 : curl_easy_cleanup (eh);
402 0 : json_decref (melt_obj);
403 0 : return GNUNET_SYSERR;
404 : }
405 0 : json_decref (melt_obj);
406 0 : mh->job = GNUNET_CURL_job_add2 (ctx,
407 : eh,
408 0 : mh->ctx.headers,
409 : &handle_melt_finished,
410 : mh);
411 0 : return GNUNET_OK;
412 : }
413 :
414 :
415 : /**
416 : * The melt request @a mh failed, return an error to
417 : * the application and cancel the operation.
418 : *
419 : * @param[in] mh melt request that failed
420 : * @param ec error code to fail with
421 : */
422 : static void
423 0 : fail_mh (struct TALER_EXCHANGE_MeltHandle *mh,
424 : enum TALER_ErrorCode ec)
425 : {
426 0 : struct TALER_EXCHANGE_MeltResponse mr = {
427 : .hr.ec = ec
428 : };
429 :
430 0 : mh->melt_cb (mh->melt_cb_cls,
431 : &mr);
432 0 : TALER_EXCHANGE_melt_cancel (mh);
433 0 : }
434 :
435 :
436 : /**
437 : * Callbacks of this type are used to serve the result of submitting a
438 : * CS R request to a exchange.
439 : *
440 : * @param cls closure with our `struct TALER_EXCHANGE_MeltHandle *`
441 : * @param csrr response details
442 : */
443 : static void
444 0 : csr_cb (void *cls,
445 : const struct TALER_EXCHANGE_CsRMeltResponse *csrr)
446 : {
447 0 : struct TALER_EXCHANGE_MeltHandle *mh = cls;
448 0 : unsigned int nks_off = 0;
449 :
450 0 : mh->csr = NULL;
451 0 : if (MHD_HTTP_OK != csrr->hr.http_status)
452 : {
453 0 : struct TALER_EXCHANGE_MeltResponse mr = {
454 : .hr = csrr->hr
455 : };
456 :
457 0 : mr.hr.hint = "/csr-melt failed";
458 0 : mh->melt_cb (mh->melt_cb_cls,
459 : &mr);
460 0 : TALER_EXCHANGE_melt_cancel (mh);
461 0 : return;
462 : }
463 0 : for (unsigned int i = 0; i<mh->rd->fresh_pks_len; i++)
464 : {
465 0 : const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
466 0 : &mh->rd->fresh_pks[i];
467 0 : struct TALER_ExchangeWithdrawValues *wv = &mh->mbds[i].alg_value;
468 :
469 0 : switch (fresh_pk->key.cipher)
470 : {
471 0 : case TALER_DENOMINATION_INVALID:
472 0 : GNUNET_break (0);
473 0 : fail_mh (mh,
474 : TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
475 0 : return;
476 0 : case TALER_DENOMINATION_RSA:
477 0 : GNUNET_assert (TALER_DENOMINATION_RSA == wv->cipher);
478 0 : break;
479 0 : case TALER_DENOMINATION_CS:
480 0 : GNUNET_assert (TALER_DENOMINATION_CS == wv->cipher);
481 0 : *wv = csrr->details.success.alg_values[nks_off];
482 0 : nks_off++;
483 0 : break;
484 : }
485 0 : }
486 0 : mh->send_rms = true;
487 0 : if (GNUNET_OK !=
488 0 : start_melt (mh))
489 : {
490 0 : GNUNET_break (0);
491 0 : fail_mh (mh,
492 : TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
493 0 : return;
494 : }
495 : }
496 :
497 :
498 : struct TALER_EXCHANGE_MeltHandle *
499 0 : TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
500 : const struct TALER_RefreshMasterSecretP *rms,
501 : const struct TALER_EXCHANGE_RefreshData *rd,
502 : TALER_EXCHANGE_MeltCallback melt_cb,
503 : void *melt_cb_cls)
504 0 : {
505 0 : struct TALER_EXCHANGE_NonceKey nks[GNUNET_NZL (rd->fresh_pks_len)];
506 0 : unsigned int nks_off = 0;
507 : struct TALER_EXCHANGE_MeltHandle *mh;
508 :
509 0 : if (0 == rd->fresh_pks_len)
510 : {
511 0 : GNUNET_break (0);
512 0 : return NULL;
513 : }
514 0 : GNUNET_assert (GNUNET_YES ==
515 : TEAH_handle_is_ready (exchange));
516 0 : mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle);
517 0 : mh->noreveal_index = TALER_CNC_KAPPA; /* invalid value */
518 0 : mh->exchange = exchange;
519 0 : mh->rd = rd;
520 0 : mh->rms = *rms;
521 0 : mh->melt_cb = melt_cb;
522 0 : mh->melt_cb_cls = melt_cb_cls;
523 0 : mh->mbds = GNUNET_new_array (rd->fresh_pks_len,
524 : struct TALER_EXCHANGE_MeltBlindingDetail);
525 0 : for (unsigned int i = 0; i<rd->fresh_pks_len; i++)
526 : {
527 0 : const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk = &rd->fresh_pks[i];
528 0 : struct TALER_ExchangeWithdrawValues *wv = &mh->mbds[i].alg_value;
529 :
530 0 : switch (fresh_pk->key.cipher)
531 : {
532 0 : case TALER_DENOMINATION_INVALID:
533 0 : GNUNET_break (0);
534 0 : GNUNET_free (mh->mbds);
535 0 : GNUNET_free (mh);
536 0 : return NULL;
537 0 : case TALER_DENOMINATION_RSA:
538 0 : wv->cipher = TALER_DENOMINATION_RSA;
539 0 : break;
540 0 : case TALER_DENOMINATION_CS:
541 0 : wv->cipher = TALER_DENOMINATION_CS;
542 0 : nks[nks_off].pk = fresh_pk;
543 0 : nks[nks_off].cnc_num = nks_off;
544 0 : nks_off++;
545 0 : break;
546 : }
547 0 : }
548 0 : if (0 != nks_off)
549 : {
550 0 : mh->csr = TALER_EXCHANGE_csr_melt (exchange,
551 : rms,
552 : nks_off,
553 : nks,
554 : &csr_cb,
555 : mh);
556 0 : if (NULL == mh->csr)
557 : {
558 0 : GNUNET_break (0);
559 0 : TALER_EXCHANGE_melt_cancel (mh);
560 0 : return NULL;
561 : }
562 0 : return mh;
563 : }
564 0 : if (GNUNET_OK !=
565 0 : start_melt (mh))
566 : {
567 0 : GNUNET_break (0);
568 0 : TALER_EXCHANGE_melt_cancel (mh);
569 0 : return NULL;
570 : }
571 0 : return mh;
572 : }
573 :
574 :
575 : void
576 0 : TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh)
577 : {
578 0 : if (NULL != mh->job)
579 : {
580 0 : GNUNET_CURL_job_cancel (mh->job);
581 0 : mh->job = NULL;
582 : }
583 0 : if (NULL != mh->csr)
584 : {
585 0 : TALER_EXCHANGE_csr_melt_cancel (mh->csr);
586 0 : mh->csr = NULL;
587 : }
588 0 : TALER_EXCHANGE_free_melt_data_ (&mh->md); /* does not free 'md' itself */
589 0 : GNUNET_free (mh->mbds);
590 0 : GNUNET_free (mh->url);
591 0 : TALER_curl_easy_post_finished (&mh->ctx);
592 0 : GNUNET_free (mh);
593 0 : }
594 :
595 :
596 : /* end of exchange_api_melt.c */
|