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