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_refreshes_reveal.c
19 : * @brief Implementation of the /refreshes/$RCH/reveal requests
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_handle.h"
31 : #include "taler_signatures.h"
32 : #include "exchange_api_curl_defaults.h"
33 : #include "exchange_api_refresh_common.h"
34 :
35 :
36 : /**
37 : * @brief A /refreshes/$RCH/reveal Handle
38 : */
39 : struct TALER_EXCHANGE_RefreshesRevealHandle
40 : {
41 :
42 : /**
43 : * The connection to exchange this request handle will use
44 : */
45 : struct TALER_EXCHANGE_Handle *exchange;
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 : * Handle for the request.
60 : */
61 : struct GNUNET_CURL_Job *job;
62 :
63 : /**
64 : * Exchange-contributed values to the operation.
65 : */
66 : struct TALER_ExchangeWithdrawValues *alg_values;
67 :
68 : /**
69 : * Function to call with the result.
70 : */
71 : TALER_EXCHANGE_RefreshesRevealCallback reveal_cb;
72 :
73 : /**
74 : * Closure for @e reveal_cb.
75 : */
76 : void *reveal_cb_cls;
77 :
78 : /**
79 : * Actual information about the melt operation.
80 : */
81 : struct MeltData md;
82 :
83 : /**
84 : * The index selected by the exchange in cut-and-choose to not be revealed.
85 : */
86 : uint16_t noreveal_index;
87 :
88 : };
89 :
90 :
91 : /**
92 : * We got a 200 OK response for the /refreshes/$RCH/reveal operation. Extract
93 : * the coin signatures and return them to the caller. The signatures we get
94 : * from the exchange is for the blinded value. Thus, we first must unblind
95 : * them and then should verify their validity.
96 : *
97 : * If everything checks out, we return the unblinded signatures
98 : * to the application via the callback.
99 : *
100 : * @param rrh operation handle
101 : * @param json reply from the exchange
102 : * @param[out] rcis array of length `num_fresh_coins`, initialized to contain the coin data
103 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
104 : */
105 : static enum GNUNET_GenericReturnValue
106 0 : refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
107 : const json_t *json,
108 : struct TALER_EXCHANGE_RevealedCoinInfo *rcis)
109 : {
110 : json_t *jsona;
111 : struct GNUNET_JSON_Specification outer_spec[] = {
112 0 : GNUNET_JSON_spec_json ("ev_sigs",
113 : &jsona),
114 0 : GNUNET_JSON_spec_end ()
115 : };
116 :
117 0 : if (GNUNET_OK !=
118 0 : GNUNET_JSON_parse (json,
119 : outer_spec,
120 : NULL, NULL))
121 : {
122 0 : GNUNET_break_op (0);
123 0 : return GNUNET_SYSERR;
124 : }
125 0 : if (! json_is_array (jsona))
126 : {
127 : /* We expected an array of coins */
128 0 : GNUNET_break_op (0);
129 0 : GNUNET_JSON_parse_free (outer_spec);
130 0 : return GNUNET_SYSERR;
131 : }
132 0 : if (rrh->md.num_fresh_coins != json_array_size (jsona))
133 : {
134 : /* Number of coins generated does not match our expectation */
135 0 : GNUNET_break_op (0);
136 0 : GNUNET_JSON_parse_free (outer_spec);
137 0 : return GNUNET_SYSERR;
138 : }
139 0 : for (unsigned int i = 0; i<rrh->md.num_fresh_coins; i++)
140 : {
141 0 : struct TALER_EXCHANGE_RevealedCoinInfo *rci =
142 0 : &rcis[i];
143 0 : const struct FreshCoinData *fcd = &rrh->md.fcds[i];
144 : const struct TALER_DenominationPublicKey *pk;
145 : json_t *jsonai;
146 : struct TALER_BlindedDenominationSignature blind_sig;
147 : struct TALER_CoinSpendPublicKeyP coin_pub;
148 : struct TALER_CoinPubHashP coin_hash;
149 : struct GNUNET_JSON_Specification spec[] = {
150 0 : TALER_JSON_spec_blinded_denom_sig ("ev_sig",
151 : &blind_sig),
152 0 : GNUNET_JSON_spec_end ()
153 : };
154 : struct TALER_FreshCoin coin;
155 : union TALER_DenominationBlindingKeyP bks;
156 :
157 0 : rci->ps = fcd->ps[rrh->noreveal_index];
158 0 : rci->bks = fcd->bks[rrh->noreveal_index];
159 0 : rci->age_commitment_proof = fcd->age_commitment_proof[rrh->noreveal_index];
160 0 : rci->h_age_commitment = NULL;
161 0 : pk = &fcd->fresh_pk;
162 0 : jsonai = json_array_get (jsona, i);
163 :
164 0 : GNUNET_assert (NULL != jsonai);
165 0 : GNUNET_assert (
166 : (NULL != rrh->md.melted_coin.age_commitment_proof) ==
167 : (NULL != rci->age_commitment_proof));
168 :
169 0 : if (NULL != rci->age_commitment_proof)
170 : {
171 0 : rci->h_age_commitment = GNUNET_new (struct TALER_AgeCommitmentHash);
172 0 : TALER_age_commitment_hash (
173 0 : &rci->age_commitment_proof->commitment,
174 : rci->h_age_commitment);
175 : }
176 :
177 0 : if (GNUNET_OK !=
178 0 : GNUNET_JSON_parse (jsonai,
179 : spec,
180 : NULL, NULL))
181 : {
182 0 : GNUNET_break_op (0);
183 0 : GNUNET_JSON_parse_free (outer_spec);
184 0 : return GNUNET_SYSERR;
185 : }
186 :
187 0 : TALER_planchet_setup_coin_priv (&rci->ps,
188 0 : &rrh->alg_values[i],
189 : &rci->coin_priv);
190 0 : TALER_planchet_blinding_secret_create (&rci->ps,
191 0 : &rrh->alg_values[i],
192 : &bks);
193 : /* needed to verify the signature, and we didn't store it earlier,
194 : hence recomputing it here... */
195 0 : GNUNET_CRYPTO_eddsa_key_get_public (&rci->coin_priv.eddsa_priv,
196 : &coin_pub.eddsa_pub);
197 0 : TALER_coin_pub_hash (&coin_pub,
198 0 : rci->h_age_commitment,
199 : &coin_hash);
200 0 : if (GNUNET_OK !=
201 0 : TALER_planchet_to_coin (pk,
202 : &blind_sig,
203 : &bks,
204 0 : &rci->coin_priv,
205 0 : rci->h_age_commitment,
206 : &coin_hash,
207 0 : &rrh->alg_values[i],
208 : &coin))
209 : {
210 0 : GNUNET_break_op (0);
211 0 : GNUNET_JSON_parse_free (spec);
212 0 : GNUNET_JSON_parse_free (outer_spec);
213 0 : return GNUNET_SYSERR;
214 : }
215 0 : GNUNET_JSON_parse_free (spec);
216 0 : rci->sig = coin.sig;
217 : }
218 0 : GNUNET_JSON_parse_free (outer_spec);
219 0 : return GNUNET_OK;
220 : }
221 :
222 :
223 : /**
224 : * Function called when we're done processing the
225 : * HTTP /refreshes/$RCH/reveal request.
226 : *
227 : * @param cls the `struct TALER_EXCHANGE_RefreshHandle`
228 : * @param response_code HTTP response code, 0 on error
229 : * @param response parsed JSON result, NULL on error
230 : */
231 : static void
232 0 : handle_refresh_reveal_finished (void *cls,
233 : long response_code,
234 : const void *response)
235 : {
236 0 : struct TALER_EXCHANGE_RefreshesRevealHandle *rrh = cls;
237 0 : const json_t *j = response;
238 0 : struct TALER_EXCHANGE_RevealResult rr = {
239 : .hr.reply = j,
240 0 : .hr.http_status = (unsigned int) response_code
241 : };
242 :
243 0 : rrh->job = NULL;
244 0 : switch (response_code)
245 : {
246 0 : case 0:
247 0 : rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
248 0 : break;
249 0 : case MHD_HTTP_OK:
250 0 : {
251 0 : struct TALER_EXCHANGE_RevealedCoinInfo rcis[rrh->md.num_fresh_coins];
252 : enum GNUNET_GenericReturnValue ret;
253 :
254 0 : memset (rcis,
255 : 0,
256 : sizeof (rcis));
257 0 : ret = refresh_reveal_ok (rrh,
258 : j,
259 : rcis);
260 0 : if (GNUNET_OK != ret)
261 : {
262 0 : rr.hr.http_status = 0;
263 0 : rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
264 0 : break;
265 : }
266 : else
267 : {
268 0 : GNUNET_assert (rrh->noreveal_index < TALER_CNC_KAPPA);
269 0 : rr.details.success.num_coins = rrh->md.num_fresh_coins;
270 0 : rr.details.success.coins = rcis;
271 0 : rrh->reveal_cb (rrh->reveal_cb_cls,
272 : &rr);
273 0 : rrh->reveal_cb = NULL;
274 : }
275 0 : for (unsigned int i = 0; i<rrh->md.num_fresh_coins; i++)
276 0 : TALER_denom_sig_free (&rcis[i].sig);
277 0 : TALER_EXCHANGE_refreshes_reveal_cancel (rrh);
278 0 : return;
279 : }
280 0 : case MHD_HTTP_BAD_REQUEST:
281 : /* This should never happen, either us or the exchange is buggy
282 : (or API version conflict); just pass JSON reply to the application */
283 0 : rr.hr.ec = TALER_JSON_get_error_code (j);
284 0 : rr.hr.hint = TALER_JSON_get_error_hint (j);
285 0 : break;
286 0 : case MHD_HTTP_CONFLICT:
287 : /* Nothing really to verify, exchange says our reveal is inconsistent
288 : with our commitment, so either side is buggy; we
289 : should pass the JSON reply to the application */
290 0 : rr.hr.ec = TALER_JSON_get_error_code (j);
291 0 : rr.hr.hint = TALER_JSON_get_error_hint (j);
292 0 : break;
293 0 : case MHD_HTTP_GONE:
294 : /* Server claims key expired or has been revoked */
295 0 : rr.hr.ec = TALER_JSON_get_error_code (j);
296 0 : rr.hr.hint = TALER_JSON_get_error_hint (j);
297 0 : break;
298 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
299 : /* Server had an internal issue; we should retry, but this API
300 : leaves this to the application */
301 0 : rr.hr.ec = TALER_JSON_get_error_code (j);
302 0 : rr.hr.hint = TALER_JSON_get_error_hint (j);
303 0 : break;
304 0 : default:
305 : /* unexpected response code */
306 0 : GNUNET_break_op (0);
307 0 : rr.hr.ec = TALER_JSON_get_error_code (j);
308 0 : rr.hr.hint = TALER_JSON_get_error_hint (j);
309 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
310 : "Unexpected response code %u/%d for exchange refreshes reveal\n",
311 : (unsigned int) response_code,
312 : (int) rr.hr.ec);
313 0 : break;
314 : }
315 0 : if (NULL != rrh->reveal_cb)
316 0 : rrh->reveal_cb (rrh->reveal_cb_cls,
317 : &rr);
318 0 : TALER_EXCHANGE_refreshes_reveal_cancel (rrh);
319 : }
320 :
321 :
322 : struct TALER_EXCHANGE_RefreshesRevealHandle *
323 0 : TALER_EXCHANGE_refreshes_reveal (
324 : struct TALER_EXCHANGE_Handle *exchange,
325 : const struct TALER_RefreshMasterSecretP *rms,
326 : const struct TALER_EXCHANGE_RefreshData *rd,
327 : unsigned int num_coins,
328 : const struct TALER_ExchangeWithdrawValues *alg_values,
329 : uint32_t noreveal_index,
330 : TALER_EXCHANGE_RefreshesRevealCallback reveal_cb,
331 : void *reveal_cb_cls)
332 : {
333 : struct TALER_EXCHANGE_RefreshesRevealHandle *rrh;
334 : json_t *transfer_privs;
335 : json_t *new_denoms_h;
336 : json_t *coin_evs;
337 : json_t *reveal_obj;
338 : json_t *link_sigs;
339 0 : json_t *old_age_commitment = NULL;
340 : CURL *eh;
341 : struct GNUNET_CURL_Context *ctx;
342 : struct MeltData md;
343 : char arg_str[sizeof (struct TALER_RefreshCommitmentP) * 2 + 32];
344 0 : bool send_rms = false;
345 :
346 0 : GNUNET_assert (num_coins == rd->fresh_pks_len);
347 0 : if (noreveal_index >= TALER_CNC_KAPPA)
348 : {
349 : /* We check this here, as it would be really bad to below just
350 : disclose all the transfer keys. Note that this error should
351 : have been caught way earlier when the exchange replied, but maybe
352 : we had some internal corruption that changed the value... */
353 0 : GNUNET_break (0);
354 0 : return NULL;
355 : }
356 0 : if (GNUNET_YES !=
357 0 : TEAH_handle_is_ready (exchange))
358 : {
359 0 : GNUNET_break (0);
360 0 : return NULL;
361 : }
362 0 : if (GNUNET_OK !=
363 0 : TALER_EXCHANGE_get_melt_data_ (rms,
364 : rd,
365 : alg_values,
366 : &md))
367 : {
368 0 : GNUNET_break (0);
369 0 : return NULL;
370 : }
371 :
372 : /* now new_denoms */
373 0 : GNUNET_assert (NULL != (new_denoms_h = json_array ()));
374 0 : GNUNET_assert (NULL != (coin_evs = json_array ()));
375 0 : GNUNET_assert (NULL != (link_sigs = json_array ()));
376 0 : for (unsigned int i = 0; i<md.num_fresh_coins; i++)
377 : {
378 0 : const struct TALER_RefreshCoinData *rcd = &md.rcd[noreveal_index][i];
379 : struct TALER_DenominationHashP denom_hash;
380 :
381 0 : if (TALER_DENOMINATION_CS == md.fcds[i].fresh_pk.cipher)
382 0 : send_rms = true;
383 0 : TALER_denom_pub_hash (&md.fcds[i].fresh_pk,
384 : &denom_hash);
385 0 : GNUNET_assert (0 ==
386 : json_array_append_new (new_denoms_h,
387 : GNUNET_JSON_from_data_auto (
388 : &denom_hash)));
389 0 : GNUNET_assert (0 ==
390 : json_array_append_new (
391 : coin_evs,
392 : GNUNET_JSON_PACK (
393 : TALER_JSON_pack_blinded_planchet (
394 : NULL,
395 : &rcd->blinded_planchet))));
396 : {
397 : struct TALER_CoinSpendSignatureP link_sig;
398 : struct TALER_BlindedCoinHashP bch;
399 :
400 0 : TALER_coin_ev_hash (&rcd->blinded_planchet,
401 : &denom_hash,
402 : &bch);
403 0 : TALER_wallet_link_sign (
404 : &denom_hash,
405 0 : &md.transfer_pub[noreveal_index],
406 : &bch,
407 : &md.melted_coin.coin_priv,
408 : &link_sig);
409 0 : GNUNET_assert (0 ==
410 : json_array_append_new (
411 : link_sigs,
412 : GNUNET_JSON_from_data_auto (&link_sig)));
413 : }
414 : }
415 :
416 : /* build array of transfer private keys */
417 0 : GNUNET_assert (NULL != (transfer_privs = json_array ()));
418 0 : for (unsigned int j = 0; j<TALER_CNC_KAPPA; j++)
419 : {
420 0 : if (j == noreveal_index)
421 : {
422 : /* This is crucial: exclude the transfer key for the noreval index! */
423 0 : continue;
424 : }
425 0 : GNUNET_assert (0 ==
426 : json_array_append_new (transfer_privs,
427 : GNUNET_JSON_from_data_auto (
428 : &md.transfer_priv[j])));
429 : }
430 :
431 : /* build array of old age commitment, if applicable */
432 0 : GNUNET_assert ((NULL == rd->melt_age_commitment_proof) ==
433 : (NULL == rd->melt_h_age_commitment));
434 0 : if (NULL != rd->melt_age_commitment_proof)
435 : {
436 0 : GNUNET_assert (NULL != (old_age_commitment = json_array ()));
437 :
438 0 : for (size_t i = 0; i < rd->melt_age_commitment_proof->commitment.num; i++)
439 : {
440 0 : GNUNET_assert (0 ==
441 : json_array_append_new (
442 : old_age_commitment,
443 : GNUNET_JSON_from_data_auto (
444 : &rd->melt_age_commitment_proof->
445 : commitment.keys[i])));
446 : }
447 : }
448 :
449 : /* build main JSON request */
450 0 : reveal_obj = GNUNET_JSON_PACK (
451 : GNUNET_JSON_pack_data_auto ("transfer_pub",
452 : &md.transfer_pub[noreveal_index]),
453 : GNUNET_JSON_pack_allow_null (
454 : send_rms
455 : ? GNUNET_JSON_pack_data_auto ("rms",
456 : rms)
457 : : GNUNET_JSON_pack_string ("rms",
458 : NULL)),
459 : GNUNET_JSON_pack_allow_null (
460 : GNUNET_JSON_pack_array_steal ("old_age_commitment",
461 : old_age_commitment)),
462 : GNUNET_JSON_pack_array_steal ("transfer_privs",
463 : transfer_privs),
464 : GNUNET_JSON_pack_array_steal ("link_sigs",
465 : link_sigs),
466 : GNUNET_JSON_pack_array_steal ("new_denoms_h",
467 : new_denoms_h),
468 : GNUNET_JSON_pack_array_steal ("coin_evs",
469 : coin_evs));
470 : {
471 : char pub_str[sizeof (struct TALER_RefreshCommitmentP) * 2];
472 : char *end;
473 :
474 0 : end = GNUNET_STRINGS_data_to_string (&md.rc,
475 : sizeof (md.rc),
476 : pub_str,
477 : sizeof (pub_str));
478 0 : *end = '\0';
479 0 : GNUNET_snprintf (arg_str,
480 : sizeof (arg_str),
481 : "/refreshes/%s/reveal",
482 : pub_str);
483 : }
484 : /* finally, we can actually issue the request */
485 0 : rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshesRevealHandle);
486 0 : rrh->exchange = exchange;
487 0 : rrh->noreveal_index = noreveal_index;
488 0 : rrh->reveal_cb = reveal_cb;
489 0 : rrh->reveal_cb_cls = reveal_cb_cls;
490 0 : rrh->md = md;
491 0 : rrh->alg_values = GNUNET_memdup (alg_values,
492 : md.num_fresh_coins
493 : * sizeof (struct
494 : TALER_ExchangeWithdrawValues));
495 0 : rrh->url = TEAH_path_to_url (rrh->exchange,
496 : arg_str);
497 0 : if (NULL == rrh->url)
498 : {
499 0 : json_decref (reveal_obj);
500 0 : TALER_EXCHANGE_free_melt_data_ (&md);
501 0 : GNUNET_free (rrh->alg_values);
502 0 : GNUNET_free (rrh);
503 0 : return NULL;
504 : }
505 :
506 0 : eh = TALER_EXCHANGE_curl_easy_get_ (rrh->url);
507 0 : if ( (NULL == eh) ||
508 : (GNUNET_OK !=
509 0 : TALER_curl_easy_post (&rrh->ctx,
510 : eh,
511 : reveal_obj)) )
512 : {
513 0 : GNUNET_break (0);
514 0 : if (NULL != eh)
515 0 : curl_easy_cleanup (eh);
516 0 : json_decref (reveal_obj);
517 0 : TALER_EXCHANGE_free_melt_data_ (&md);
518 0 : GNUNET_free (rrh->alg_values);
519 0 : GNUNET_free (rrh->url);
520 0 : GNUNET_free (rrh);
521 0 : return NULL;
522 : }
523 0 : json_decref (reveal_obj);
524 0 : ctx = TEAH_handle_to_context (rrh->exchange);
525 0 : rrh->job = GNUNET_CURL_job_add2 (ctx,
526 : eh,
527 0 : rrh->ctx.headers,
528 : &handle_refresh_reveal_finished,
529 : rrh);
530 0 : return rrh;
531 : }
532 :
533 :
534 : void
535 0 : TALER_EXCHANGE_refreshes_reveal_cancel (
536 : struct TALER_EXCHANGE_RefreshesRevealHandle *rrh)
537 : {
538 0 : if (NULL != rrh->job)
539 : {
540 0 : GNUNET_CURL_job_cancel (rrh->job);
541 0 : rrh->job = NULL;
542 : }
543 0 : GNUNET_free (rrh->alg_values);
544 0 : GNUNET_free (rrh->url);
545 0 : TALER_curl_easy_post_finished (&rrh->ctx);
546 0 : TALER_EXCHANGE_free_melt_data_ (&rrh->md);
547 0 : GNUNET_free (rrh);
548 0 : }
549 :
550 :
551 : /* exchange_api_refreshes_reveal.c */
|