Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-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_reserves_open.c
19 : * @brief Implementation of the POST /reserves/$RESERVE_PUB/open requests
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <jansson.h>
24 : #include <microhttpd.h> /* just for HTTP open 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_exchange_service.h"
29 : #include "taler_json_lib.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 :
35 :
36 : /**
37 : * Information we keep per coin to validate the reply.
38 : */
39 : struct CoinData
40 : {
41 : /**
42 : * Public key of the coin.
43 : */
44 : struct TALER_CoinSpendPublicKeyP coin_pub;
45 :
46 : /**
47 : * Signature by the coin.
48 : */
49 : struct TALER_CoinSpendSignatureP coin_sig;
50 :
51 : /**
52 : * The hash of the denomination's public key
53 : */
54 : struct TALER_DenominationHashP h_denom_pub;
55 :
56 : /**
57 : * How much did this coin contribute.
58 : */
59 : struct TALER_Amount contribution;
60 : };
61 :
62 :
63 : /**
64 : * @brief A /reserves/$RID/open Handle
65 : */
66 : struct TALER_EXCHANGE_ReservesOpenHandle
67 : {
68 :
69 : /**
70 : * The keys of the exchange this request handle will use
71 : */
72 : struct TALER_EXCHANGE_Keys *keys;
73 :
74 : /**
75 : * The url for this request.
76 : */
77 : char *url;
78 :
79 : /**
80 : * Handle for the request.
81 : */
82 : struct GNUNET_CURL_Job *job;
83 :
84 : /**
85 : * Context for #TEH_curl_easy_post(). Keeps the data that must
86 : * persist for Curl to make the upload.
87 : */
88 : struct TALER_CURL_PostContext post_ctx;
89 :
90 : /**
91 : * Function to call with the result.
92 : */
93 : TALER_EXCHANGE_ReservesOpenCallback cb;
94 :
95 : /**
96 : * Closure for @a cb.
97 : */
98 : void *cb_cls;
99 :
100 : /**
101 : * Information we keep per coin to validate the reply.
102 : */
103 : struct CoinData *coins;
104 :
105 : /**
106 : * Length of the @e coins array.
107 : */
108 : unsigned int num_coins;
109 :
110 : /**
111 : * Public key of the reserve we are querying.
112 : */
113 : struct TALER_ReservePublicKeyP reserve_pub;
114 :
115 : /**
116 : * Our signature.
117 : */
118 : struct TALER_ReserveSignatureP reserve_sig;
119 :
120 : /**
121 : * When did we make the request.
122 : */
123 : struct GNUNET_TIME_Timestamp ts;
124 :
125 : };
126 :
127 :
128 : /**
129 : * We received an #MHD_HTTP_OK open code. Handle the JSON
130 : * response.
131 : *
132 : * @param roh handle of the request
133 : * @param j JSON response
134 : * @return #GNUNET_OK on success
135 : */
136 : static enum GNUNET_GenericReturnValue
137 4 : handle_reserves_open_ok (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
138 : const json_t *j)
139 : {
140 4 : struct TALER_EXCHANGE_ReserveOpenResult rs = {
141 : .hr.reply = j,
142 : .hr.http_status = MHD_HTTP_OK,
143 : };
144 : struct GNUNET_JSON_Specification spec[] = {
145 4 : TALER_JSON_spec_amount_any ("open_cost",
146 : &rs.details.ok.open_cost),
147 4 : GNUNET_JSON_spec_timestamp ("reserve_expiration",
148 : &rs.details.ok.expiration_time),
149 4 : GNUNET_JSON_spec_end ()
150 : };
151 :
152 4 : if (GNUNET_OK !=
153 4 : GNUNET_JSON_parse (j,
154 : spec,
155 : NULL,
156 : NULL))
157 : {
158 0 : GNUNET_break_op (0);
159 0 : return GNUNET_SYSERR;
160 : }
161 4 : roh->cb (roh->cb_cls,
162 : &rs);
163 4 : roh->cb = NULL;
164 4 : GNUNET_JSON_parse_free (spec);
165 4 : return GNUNET_OK;
166 : }
167 :
168 :
169 : /**
170 : * We received an #MHD_HTTP_PAYMENT_REQUIRED open code. Handle the JSON
171 : * response.
172 : *
173 : * @param roh handle of the request
174 : * @param j JSON response
175 : * @return #GNUNET_OK on success
176 : */
177 : static enum GNUNET_GenericReturnValue
178 2 : handle_reserves_open_pr (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
179 : const json_t *j)
180 : {
181 2 : struct TALER_EXCHANGE_ReserveOpenResult rs = {
182 : .hr.reply = j,
183 : .hr.http_status = MHD_HTTP_PAYMENT_REQUIRED,
184 : };
185 : struct GNUNET_JSON_Specification spec[] = {
186 2 : TALER_JSON_spec_amount_any ("open_cost",
187 : &rs.details.payment_required.open_cost),
188 2 : GNUNET_JSON_spec_timestamp ("reserve_expiration",
189 : &rs.details.payment_required.expiration_time),
190 2 : GNUNET_JSON_spec_end ()
191 : };
192 :
193 2 : if (GNUNET_OK !=
194 2 : GNUNET_JSON_parse (j,
195 : spec,
196 : NULL,
197 : NULL))
198 : {
199 0 : GNUNET_break_op (0);
200 0 : return GNUNET_SYSERR;
201 : }
202 2 : roh->cb (roh->cb_cls,
203 : &rs);
204 2 : roh->cb = NULL;
205 2 : GNUNET_JSON_parse_free (spec);
206 2 : return GNUNET_OK;
207 : }
208 :
209 :
210 : /**
211 : * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS open code. Handle the JSON
212 : * response.
213 : *
214 : * @param roh handle of the request
215 : * @param j JSON response
216 : * @return #GNUNET_OK on success
217 : */
218 : static enum GNUNET_GenericReturnValue
219 0 : handle_reserves_open_kyc (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
220 : const json_t *j)
221 : {
222 0 : struct TALER_EXCHANGE_ReserveOpenResult rs = {
223 : .hr.reply = j,
224 : .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
225 : };
226 : struct GNUNET_JSON_Specification spec[] = {
227 0 : GNUNET_JSON_spec_fixed_auto (
228 : "h_payto",
229 : &rs.details.unavailable_for_legal_reasons.h_payto),
230 0 : GNUNET_JSON_spec_uint64 (
231 : "requirement_row",
232 : &rs.details.unavailable_for_legal_reasons.requirement_row),
233 0 : GNUNET_JSON_spec_end ()
234 : };
235 :
236 0 : if (GNUNET_OK !=
237 0 : GNUNET_JSON_parse (j,
238 : spec,
239 : NULL,
240 : NULL))
241 : {
242 0 : GNUNET_break_op (0);
243 0 : return GNUNET_SYSERR;
244 : }
245 0 : roh->cb (roh->cb_cls,
246 : &rs);
247 0 : roh->cb = NULL;
248 0 : GNUNET_JSON_parse_free (spec);
249 0 : return GNUNET_OK;
250 : }
251 :
252 :
253 : /**
254 : * Function called when we're done processing the
255 : * HTTP /reserves/$RID/open request.
256 : *
257 : * @param cls the `struct TALER_EXCHANGE_ReservesOpenHandle`
258 : * @param response_code HTTP response code, 0 on error
259 : * @param response parsed JSON result, NULL on error
260 : */
261 : static void
262 6 : handle_reserves_open_finished (void *cls,
263 : long response_code,
264 : const void *response)
265 : {
266 6 : struct TALER_EXCHANGE_ReservesOpenHandle *roh = cls;
267 6 : const json_t *j = response;
268 6 : struct TALER_EXCHANGE_ReserveOpenResult rs = {
269 : .hr.reply = j,
270 6 : .hr.http_status = (unsigned int) response_code
271 : };
272 :
273 6 : roh->job = NULL;
274 6 : switch (response_code)
275 : {
276 0 : case 0:
277 0 : rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
278 0 : break;
279 4 : case MHD_HTTP_OK:
280 4 : if (GNUNET_OK !=
281 4 : handle_reserves_open_ok (roh,
282 : j))
283 : {
284 0 : GNUNET_break_op (0);
285 0 : rs.hr.http_status = 0;
286 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
287 : }
288 4 : break;
289 0 : case MHD_HTTP_BAD_REQUEST:
290 : /* This should never happen, either us or the exchange is buggy
291 : (or API version conflict); just pass JSON reply to the application */
292 0 : GNUNET_break (0);
293 0 : json_dumpf (j,
294 : stderr,
295 : JSON_INDENT (2));
296 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
297 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
298 0 : break;
299 2 : case MHD_HTTP_PAYMENT_REQUIRED:
300 2 : if (GNUNET_OK !=
301 2 : handle_reserves_open_pr (roh,
302 : j))
303 : {
304 0 : GNUNET_break_op (0);
305 0 : rs.hr.http_status = 0;
306 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
307 : }
308 2 : break;
309 0 : case MHD_HTTP_FORBIDDEN:
310 : /* This should never happen, either us or the exchange is buggy
311 : (or API version conflict); just pass JSON reply to the application */
312 0 : GNUNET_break (0);
313 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
314 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
315 0 : break;
316 0 : case MHD_HTTP_NOT_FOUND:
317 : /* Nothing really to verify, this should never
318 : happen, we should pass the JSON reply to the application */
319 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
320 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
321 0 : break;
322 0 : case MHD_HTTP_CONFLICT:
323 : {
324 0 : const struct CoinData *cd = NULL;
325 : struct GNUNET_JSON_Specification spec[] = {
326 0 : GNUNET_JSON_spec_fixed_auto ("coin_pub",
327 : &rs.details.conflict.coin_pub),
328 0 : GNUNET_JSON_spec_end ()
329 : };
330 :
331 0 : if (GNUNET_OK !=
332 0 : GNUNET_JSON_parse (j,
333 : spec,
334 : NULL,
335 : NULL))
336 : {
337 0 : GNUNET_break_op (0);
338 0 : rs.hr.http_status = 0;
339 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
340 0 : break;
341 : }
342 0 : for (unsigned int i = 0; i<roh->num_coins; i++)
343 : {
344 0 : const struct CoinData *cdi = &roh->coins[i];
345 :
346 0 : if (0 == GNUNET_memcmp (&rs.details.conflict.coin_pub,
347 : &cdi->coin_pub))
348 : {
349 0 : cd = cdi;
350 0 : break;
351 : }
352 : }
353 0 : if (NULL == cd)
354 : {
355 0 : GNUNET_break_op (0);
356 0 : rs.hr.http_status = 0;
357 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
358 0 : break;
359 : }
360 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
361 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
362 0 : break;
363 : }
364 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
365 0 : if (GNUNET_OK !=
366 0 : handle_reserves_open_kyc (roh,
367 : j))
368 : {
369 0 : GNUNET_break_op (0);
370 0 : rs.hr.http_status = 0;
371 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
372 : }
373 0 : break;
374 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
375 : /* Server had an internal issue; we should retry, but this API
376 : leaves this to the application */
377 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
378 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
379 0 : break;
380 0 : default:
381 : /* unexpected response code */
382 0 : GNUNET_break_op (0);
383 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
384 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
385 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
386 : "Unexpected response code %u/%d for reserves open\n",
387 : (unsigned int) response_code,
388 : (int) rs.hr.ec);
389 0 : break;
390 : }
391 6 : if (NULL != roh->cb)
392 : {
393 0 : roh->cb (roh->cb_cls,
394 : &rs);
395 0 : roh->cb = NULL;
396 : }
397 6 : TALER_EXCHANGE_reserves_open_cancel (roh);
398 6 : }
399 :
400 :
401 : struct TALER_EXCHANGE_ReservesOpenHandle *
402 6 : TALER_EXCHANGE_reserves_open (
403 : struct GNUNET_CURL_Context *ctx,
404 : const char *url,
405 : struct TALER_EXCHANGE_Keys *keys,
406 : const struct TALER_ReservePrivateKeyP *reserve_priv,
407 : const struct TALER_Amount *reserve_contribution,
408 : unsigned int coin_payments_length,
409 : const struct TALER_EXCHANGE_PurseDeposit coin_payments[
410 : static coin_payments_length],
411 : struct GNUNET_TIME_Timestamp expiration_time,
412 : uint32_t min_purses,
413 : TALER_EXCHANGE_ReservesOpenCallback cb,
414 : void *cb_cls)
415 6 : {
416 : struct TALER_EXCHANGE_ReservesOpenHandle *roh;
417 : CURL *eh;
418 : char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
419 : json_t *cpa;
420 :
421 6 : roh = GNUNET_new (struct TALER_EXCHANGE_ReservesOpenHandle);
422 6 : roh->cb = cb;
423 6 : roh->cb_cls = cb_cls;
424 6 : roh->ts = GNUNET_TIME_timestamp_get ();
425 6 : GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
426 : &roh->reserve_pub.eddsa_pub);
427 : {
428 : char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
429 : char *end;
430 :
431 6 : end = GNUNET_STRINGS_data_to_string (
432 6 : &roh->reserve_pub,
433 : sizeof (roh->reserve_pub),
434 : pub_str,
435 : sizeof (pub_str));
436 6 : *end = '\0';
437 6 : GNUNET_snprintf (arg_str,
438 : sizeof (arg_str),
439 : "reserves/%s/open",
440 : pub_str);
441 : }
442 6 : roh->url = TALER_url_join (url,
443 : arg_str,
444 : NULL);
445 6 : if (NULL == roh->url)
446 : {
447 0 : GNUNET_free (roh);
448 0 : return NULL;
449 : }
450 6 : eh = TALER_EXCHANGE_curl_easy_get_ (roh->url);
451 6 : if (NULL == eh)
452 : {
453 0 : GNUNET_break (0);
454 0 : GNUNET_free (roh->url);
455 0 : GNUNET_free (roh);
456 0 : return NULL;
457 : }
458 6 : TALER_wallet_reserve_open_sign (reserve_contribution,
459 : roh->ts,
460 : expiration_time,
461 : min_purses,
462 : reserve_priv,
463 : &roh->reserve_sig);
464 6 : roh->coins = GNUNET_new_array (coin_payments_length,
465 : struct CoinData);
466 6 : cpa = json_array ();
467 6 : GNUNET_assert (NULL != cpa);
468 8 : for (unsigned int i = 0; i<coin_payments_length; i++)
469 : {
470 2 : const struct TALER_EXCHANGE_PurseDeposit *pd = &coin_payments[i];
471 2 : const struct TALER_AgeCommitmentProof *acp = pd->age_commitment_proof;
472 : struct TALER_AgeCommitmentHash ahac;
473 2 : struct TALER_AgeCommitmentHash *achp = NULL;
474 2 : struct CoinData *cd = &roh->coins[i];
475 : json_t *cp;
476 :
477 2 : cd->contribution = pd->amount;
478 2 : cd->h_denom_pub = pd->h_denom_pub;
479 2 : if (NULL != acp)
480 : {
481 0 : TALER_age_commitment_hash (&acp->commitment,
482 : &ahac);
483 0 : achp = &ahac;
484 : }
485 2 : TALER_wallet_reserve_open_deposit_sign (&pd->amount,
486 2 : &roh->reserve_sig,
487 : &pd->coin_priv,
488 : &cd->coin_sig);
489 2 : GNUNET_CRYPTO_eddsa_key_get_public (&pd->coin_priv.eddsa_priv,
490 : &cd->coin_pub.eddsa_pub);
491 :
492 2 : cp = GNUNET_JSON_PACK (
493 : GNUNET_JSON_pack_allow_null (
494 : GNUNET_JSON_pack_data_auto ("h_age_commitment",
495 : achp)),
496 : TALER_JSON_pack_amount ("amount",
497 : &pd->amount),
498 : GNUNET_JSON_pack_data_auto ("denom_pub_hash",
499 : &pd->h_denom_pub),
500 : TALER_JSON_pack_denom_sig ("ub_sig",
501 : &pd->denom_sig),
502 : GNUNET_JSON_pack_data_auto ("coin_pub",
503 : &cd->coin_pub),
504 : GNUNET_JSON_pack_data_auto ("coin_sig",
505 : &cd->coin_sig));
506 2 : GNUNET_assert (0 ==
507 : json_array_append_new (cpa,
508 : cp));
509 : }
510 : {
511 6 : json_t *open_obj = GNUNET_JSON_PACK (
512 : GNUNET_JSON_pack_timestamp ("request_timestamp",
513 : roh->ts),
514 : GNUNET_JSON_pack_timestamp ("reserve_expiration",
515 : expiration_time),
516 : GNUNET_JSON_pack_array_steal ("payments",
517 : cpa),
518 : TALER_JSON_pack_amount ("reserve_payment",
519 : reserve_contribution),
520 : GNUNET_JSON_pack_uint64 ("purse_limit",
521 : min_purses),
522 : GNUNET_JSON_pack_data_auto ("reserve_sig",
523 : &roh->reserve_sig));
524 :
525 6 : if (GNUNET_OK !=
526 6 : TALER_curl_easy_post (&roh->post_ctx,
527 : eh,
528 : open_obj))
529 : {
530 0 : GNUNET_break (0);
531 0 : curl_easy_cleanup (eh);
532 0 : json_decref (open_obj);
533 0 : GNUNET_free (roh->coins);
534 0 : GNUNET_free (roh->url);
535 0 : GNUNET_free (roh);
536 0 : return NULL;
537 : }
538 6 : json_decref (open_obj);
539 : }
540 6 : roh->keys = TALER_EXCHANGE_keys_incref (keys);
541 12 : roh->job = GNUNET_CURL_job_add2 (ctx,
542 : eh,
543 6 : roh->post_ctx.headers,
544 : &handle_reserves_open_finished,
545 : roh);
546 6 : return roh;
547 : }
548 :
549 :
550 : void
551 6 : TALER_EXCHANGE_reserves_open_cancel (
552 : struct TALER_EXCHANGE_ReservesOpenHandle *roh)
553 : {
554 6 : if (NULL != roh->job)
555 : {
556 0 : GNUNET_CURL_job_cancel (roh->job);
557 0 : roh->job = NULL;
558 : }
559 6 : TALER_curl_easy_post_finished (&roh->post_ctx);
560 6 : GNUNET_free (roh->coins);
561 6 : GNUNET_free (roh->url);
562 6 : TALER_EXCHANGE_keys_decref (roh->keys);
563 6 : GNUNET_free (roh);
564 6 : }
565 :
566 :
567 : /* end of exchange_api_reserves_open.c */
|