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