Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 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_purse_create_with_merge.c
19 : * @brief Implementation of the client to create a
20 : * purse for an account
21 : * @author Christian Grothoff
22 : */
23 : #include "platform.h"
24 : #include <jansson.h>
25 : #include <microhttpd.h> /* just for HTTP status codes */
26 : #include <gnunet/gnunet_util_lib.h>
27 : #include <gnunet/gnunet_json_lib.h>
28 : #include <gnunet/gnunet_curl_lib.h>
29 : #include "taler_json_lib.h"
30 : #include "taler_exchange_service.h"
31 : #include "exchange_api_handle.h"
32 : #include "exchange_api_common.h"
33 : #include "taler_signatures.h"
34 : #include "exchange_api_curl_defaults.h"
35 :
36 :
37 : /**
38 : * @brief A purse create with merge handle
39 : */
40 : struct TALER_EXCHANGE_PurseCreateMergeHandle
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 the result.
66 : */
67 : TALER_EXCHANGE_PurseCreateMergeCallback cb;
68 :
69 : /**
70 : * Closure for @a cb.
71 : */
72 : void *cb_cls;
73 :
74 : /**
75 : * The encrypted contract (if any).
76 : */
77 : struct TALER_EncryptedContract econtract;
78 :
79 : /**
80 : * Expected value in the purse after fees.
81 : */
82 : struct TALER_Amount purse_value_after_fees;
83 :
84 : /**
85 : * Public key of the reserve public key.
86 : */
87 : struct TALER_ReservePublicKeyP reserve_pub;
88 :
89 : /**
90 : * Reserve signature affirming our merge.
91 : */
92 : struct TALER_ReserveSignatureP reserve_sig;
93 :
94 : /**
95 : * Merge capability key.
96 : */
97 : struct TALER_PurseMergePublicKeyP merge_pub;
98 :
99 : /**
100 : * Our merge signature (if any).
101 : */
102 : struct TALER_PurseMergeSignatureP merge_sig;
103 :
104 : /**
105 : * Public key of the purse.
106 : */
107 : struct TALER_PurseContractPublicKeyP purse_pub;
108 :
109 : /**
110 : * Request data we signed over.
111 : */
112 : struct TALER_PurseContractSignatureP purse_sig;
113 :
114 : /**
115 : * Hash over the purse's contrac terms.
116 : */
117 : struct TALER_PrivateContractHashP h_contract_terms;
118 :
119 : /**
120 : * When does the purse expire.
121 : */
122 : struct GNUNET_TIME_Timestamp purse_expiration;
123 :
124 : /**
125 : * When does the purse get merged/created.
126 : */
127 : struct GNUNET_TIME_Timestamp merge_timestamp;
128 : };
129 :
130 :
131 : /**
132 : * Function called when we're done processing the
133 : * HTTP /reserves/$RID/purse request.
134 : *
135 : * @param cls the `struct TALER_EXCHANGE_PurseCreateMergeHandle`
136 : * @param response_code HTTP response code, 0 on error
137 : * @param response parsed JSON result, NULL on error
138 : */
139 : static void
140 0 : handle_purse_create_with_merge_finished (void *cls,
141 : long response_code,
142 : const void *response)
143 : {
144 0 : struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm = cls;
145 0 : const json_t *j = response;
146 0 : struct TALER_EXCHANGE_PurseCreateMergeResponse dr = {
147 : .hr.reply = j,
148 0 : .hr.http_status = (unsigned int) response_code,
149 0 : .reserve_sig = &pcm->reserve_sig
150 : };
151 :
152 0 : pcm->job = NULL;
153 0 : switch (response_code)
154 : {
155 0 : case 0:
156 0 : dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
157 0 : break;
158 0 : case MHD_HTTP_OK:
159 : {
160 : const struct TALER_EXCHANGE_Keys *key_state;
161 : struct GNUNET_TIME_Timestamp etime;
162 : struct TALER_Amount total_deposited;
163 : struct TALER_ExchangeSignatureP exchange_sig;
164 : struct TALER_ExchangePublicKeyP exchange_pub;
165 : struct GNUNET_JSON_Specification spec[] = {
166 0 : TALER_JSON_spec_amount_any ("total_deposited",
167 : &total_deposited),
168 0 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
169 : &exchange_sig),
170 0 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
171 : &exchange_pub),
172 0 : GNUNET_JSON_spec_timestamp ("exchange_timestamp",
173 : &etime),
174 0 : GNUNET_JSON_spec_end ()
175 : };
176 :
177 0 : if (GNUNET_OK !=
178 0 : GNUNET_JSON_parse (j,
179 : spec,
180 : NULL, NULL))
181 : {
182 0 : GNUNET_break_op (0);
183 0 : dr.hr.http_status = 0;
184 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
185 0 : break;
186 : }
187 0 : key_state = TALER_EXCHANGE_get_keys (pcm->exchange);
188 0 : if (GNUNET_OK !=
189 0 : TALER_EXCHANGE_test_signing_key (key_state,
190 : &exchange_pub))
191 : {
192 0 : GNUNET_break_op (0);
193 0 : dr.hr.http_status = 0;
194 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
195 0 : break;
196 : }
197 0 : if (GNUNET_OK !=
198 0 : TALER_exchange_online_purse_created_verify (
199 : etime,
200 : pcm->purse_expiration,
201 0 : &pcm->purse_value_after_fees,
202 : &total_deposited,
203 0 : &pcm->purse_pub,
204 0 : &pcm->h_contract_terms,
205 : &exchange_pub,
206 : &exchange_sig))
207 : {
208 0 : GNUNET_break_op (0);
209 0 : dr.hr.http_status = 0;
210 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
211 0 : break;
212 : }
213 : }
214 0 : break;
215 0 : case MHD_HTTP_BAD_REQUEST:
216 : /* This should never happen, either us or the exchange is buggy
217 : (or API version conflict); just pass JSON reply to the application */
218 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
219 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
220 0 : break;
221 0 : case MHD_HTTP_FORBIDDEN:
222 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
223 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
224 : /* Nothing really to verify, exchange says one of the signatures is
225 : invalid; as we checked them, this should never happen, we
226 : should pass the JSON reply to the application */
227 0 : break;
228 0 : case MHD_HTTP_NOT_FOUND:
229 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
230 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
231 : /* Nothing really to verify, this should never
232 : happen, we should pass the JSON reply to the application */
233 0 : break;
234 0 : case MHD_HTTP_CONFLICT:
235 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
236 0 : switch (dr.hr.ec)
237 : {
238 0 : case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA:
239 0 : if (GNUNET_OK !=
240 0 : TALER_EXCHANGE_check_purse_create_conflict_ (
241 0 : &pcm->purse_sig,
242 0 : &pcm->purse_pub,
243 : j))
244 : {
245 0 : dr.hr.http_status = 0;
246 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
247 0 : break;
248 : }
249 0 : break;
250 0 : case TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA:
251 0 : if (GNUNET_OK !=
252 0 : TALER_EXCHANGE_check_purse_merge_conflict_ (
253 0 : &pcm->merge_sig,
254 0 : &pcm->merge_pub,
255 0 : &pcm->purse_pub,
256 0 : pcm->exchange->url,
257 : j))
258 : {
259 0 : GNUNET_break_op (0);
260 0 : dr.hr.http_status = 0;
261 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
262 0 : break;
263 : }
264 0 : break;
265 0 : case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS:
266 : /* nothing to verify */
267 0 : break;
268 0 : case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA:
269 0 : if (GNUNET_OK !=
270 0 : TALER_EXCHANGE_check_purse_econtract_conflict_ (
271 0 : &pcm->econtract.econtract_sig,
272 0 : &pcm->purse_pub,
273 : j))
274 : {
275 0 : GNUNET_break_op (0);
276 0 : dr.hr.http_status = 0;
277 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
278 0 : break;
279 : }
280 0 : break;
281 0 : default:
282 : /* unexpected EC! */
283 0 : GNUNET_break_op (0);
284 0 : dr.hr.http_status = 0;
285 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
286 0 : break;
287 : } /* end inner (EC) switch */
288 0 : break;
289 0 : case MHD_HTTP_GONE:
290 : /* could happen if denomination was revoked */
291 : /* Note: one might want to check /keys for revocation
292 : signature here, alas tricky in case our /keys
293 : is outdated => left to clients */
294 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
295 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
296 0 : break;
297 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
298 : {
299 : struct GNUNET_JSON_Specification spec[] = {
300 0 : GNUNET_JSON_spec_uint64 (
301 : "requirement_row",
302 : &dr.details.unavailable_for_legal_reasons.requirement_row),
303 0 : GNUNET_JSON_spec_end ()
304 : };
305 :
306 0 : if (GNUNET_OK !=
307 0 : GNUNET_JSON_parse (j,
308 : spec,
309 : NULL, NULL))
310 : {
311 0 : GNUNET_break_op (0);
312 0 : dr.hr.http_status = 0;
313 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
314 0 : break;
315 : }
316 : }
317 0 : break;
318 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
319 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
320 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
321 : /* Server had an internal issue; we should retry, but this API
322 : leaves this to the application */
323 0 : break;
324 0 : default:
325 : /* unexpected response code */
326 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
327 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
328 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
329 : "Unexpected response code %u/%d for exchange deposit\n",
330 : (unsigned int) response_code,
331 : dr.hr.ec);
332 0 : GNUNET_break_op (0);
333 0 : break;
334 : }
335 0 : pcm->cb (pcm->cb_cls,
336 : &dr);
337 0 : TALER_EXCHANGE_purse_create_with_merge_cancel (pcm);
338 0 : }
339 :
340 :
341 : struct TALER_EXCHANGE_PurseCreateMergeHandle *
342 0 : TALER_EXCHANGE_purse_create_with_merge (
343 : struct TALER_EXCHANGE_Handle *exchange,
344 : const struct TALER_ReservePrivateKeyP *reserve_priv,
345 : const struct TALER_PurseContractPrivateKeyP *purse_priv,
346 : const struct TALER_PurseMergePrivateKeyP *merge_priv,
347 : const struct TALER_ContractDiffiePrivateP *contract_priv,
348 : const json_t *contract_terms,
349 : bool upload_contract,
350 : bool pay_for_purse,
351 : struct GNUNET_TIME_Timestamp merge_timestamp,
352 : TALER_EXCHANGE_PurseCreateMergeCallback cb,
353 : void *cb_cls)
354 : {
355 : struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm;
356 : struct GNUNET_CURL_Context *ctx;
357 : json_t *create_with_merge_obj;
358 : CURL *eh;
359 : char arg_str[sizeof (pcm->reserve_pub) * 2 + 32];
360 0 : uint32_t min_age = 0;
361 : struct TALER_Amount purse_fee;
362 : enum TALER_WalletAccountMergeFlags flags;
363 :
364 0 : pcm = GNUNET_new (struct TALER_EXCHANGE_PurseCreateMergeHandle);
365 0 : pcm->exchange = exchange;
366 0 : pcm->cb = cb;
367 0 : pcm->cb_cls = cb_cls;
368 0 : if (GNUNET_OK !=
369 0 : TALER_JSON_contract_hash (contract_terms,
370 : &pcm->h_contract_terms))
371 : {
372 0 : GNUNET_break (0);
373 0 : GNUNET_free (pcm);
374 0 : return NULL;
375 : }
376 0 : pcm->merge_timestamp = merge_timestamp;
377 0 : GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
378 : &pcm->purse_pub.eddsa_pub);
379 0 : GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
380 : &pcm->reserve_pub.eddsa_pub);
381 0 : GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
382 : &pcm->merge_pub.eddsa_pub);
383 :
384 : {
385 : struct GNUNET_JSON_Specification spec[] = {
386 0 : TALER_JSON_spec_amount_any ("amount",
387 : &pcm->purse_value_after_fees),
388 0 : GNUNET_JSON_spec_mark_optional (
389 : GNUNET_JSON_spec_uint32 ("minimum_age",
390 : &min_age),
391 : NULL),
392 0 : GNUNET_JSON_spec_timestamp ("pay_deadline",
393 : &pcm->purse_expiration),
394 0 : GNUNET_JSON_spec_end ()
395 : };
396 :
397 0 : if (GNUNET_OK !=
398 0 : GNUNET_JSON_parse (contract_terms,
399 : spec,
400 : NULL, NULL))
401 : {
402 0 : GNUNET_break (0);
403 0 : GNUNET_free (pcm);
404 0 : return NULL;
405 : }
406 : }
407 0 : if (pay_for_purse)
408 : {
409 : const struct TALER_EXCHANGE_GlobalFee *gf;
410 :
411 0 : gf = TALER_EXCHANGE_get_global_fee (
412 : TALER_EXCHANGE_get_keys (exchange),
413 : GNUNET_TIME_timestamp_get ());
414 0 : purse_fee = gf->fees.purse;
415 0 : flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
416 : }
417 : else
418 : {
419 0 : GNUNET_assert (GNUNET_OK ==
420 : TALER_amount_set_zero (pcm->purse_value_after_fees.currency,
421 : &purse_fee));
422 0 : flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA;
423 : }
424 :
425 0 : GNUNET_assert (GNUNET_YES ==
426 : TEAH_handle_is_ready (exchange));
427 : {
428 : char pub_str[sizeof (pcm->reserve_pub) * 2];
429 : char *end;
430 :
431 0 : end = GNUNET_STRINGS_data_to_string (
432 0 : &pcm->reserve_pub,
433 : sizeof (pcm->reserve_pub),
434 : pub_str,
435 : sizeof (pub_str));
436 0 : *end = '\0';
437 0 : GNUNET_snprintf (arg_str,
438 : sizeof (arg_str),
439 : "/reserves/%s/purse",
440 : pub_str);
441 : }
442 0 : pcm->url = TEAH_path_to_url (exchange,
443 : arg_str);
444 0 : if (NULL == pcm->url)
445 : {
446 0 : GNUNET_break (0);
447 0 : GNUNET_free (pcm);
448 0 : return NULL;
449 : }
450 0 : TALER_wallet_purse_create_sign (pcm->purse_expiration,
451 : &pcm->h_contract_terms,
452 0 : &pcm->merge_pub,
453 : min_age,
454 0 : &pcm->purse_value_after_fees,
455 : purse_priv,
456 : &pcm->purse_sig);
457 : {
458 : char *payto_uri;
459 :
460 0 : payto_uri = TALER_reserve_make_payto (exchange->url,
461 0 : &pcm->reserve_pub);
462 0 : TALER_wallet_purse_merge_sign (payto_uri,
463 : merge_timestamp,
464 0 : &pcm->purse_pub,
465 : merge_priv,
466 : &pcm->merge_sig);
467 0 : GNUNET_free (payto_uri);
468 : }
469 0 : TALER_wallet_account_merge_sign (merge_timestamp,
470 0 : &pcm->purse_pub,
471 : pcm->purse_expiration,
472 0 : &pcm->h_contract_terms,
473 0 : &pcm->purse_value_after_fees,
474 : &purse_fee,
475 : min_age,
476 : flags,
477 : reserve_priv,
478 : &pcm->reserve_sig);
479 0 : if (upload_contract)
480 : {
481 0 : TALER_CRYPTO_contract_encrypt_for_deposit (
482 0 : &pcm->purse_pub,
483 : contract_priv,
484 : contract_terms,
485 : &pcm->econtract.econtract,
486 : &pcm->econtract.econtract_size);
487 0 : GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
488 : &pcm->econtract.contract_pub.ecdhe_pub);
489 0 : TALER_wallet_econtract_upload_sign (
490 0 : pcm->econtract.econtract,
491 : pcm->econtract.econtract_size,
492 0 : &pcm->econtract.contract_pub,
493 : purse_priv,
494 : &pcm->econtract.econtract_sig);
495 : }
496 0 : create_with_merge_obj = GNUNET_JSON_PACK (
497 : TALER_JSON_pack_amount ("purse_value",
498 : &pcm->purse_value_after_fees),
499 : GNUNET_JSON_pack_uint64 ("min_age",
500 : min_age),
501 : GNUNET_JSON_pack_allow_null (
502 : TALER_JSON_pack_econtract ("econtract",
503 : upload_contract
504 : ? &pcm->econtract
505 : : NULL)),
506 : GNUNET_JSON_pack_allow_null (
507 : pay_for_purse
508 : ? TALER_JSON_pack_amount ("purse_fee",
509 : &purse_fee)
510 : : GNUNET_JSON_pack_string ("dummy2",
511 : NULL)),
512 : GNUNET_JSON_pack_data_auto ("merge_pub",
513 : &pcm->merge_pub),
514 : GNUNET_JSON_pack_data_auto ("merge_sig",
515 : &pcm->merge_sig),
516 : GNUNET_JSON_pack_data_auto ("reserve_sig",
517 : &pcm->reserve_sig),
518 : GNUNET_JSON_pack_data_auto ("purse_pub",
519 : &pcm->purse_pub),
520 : GNUNET_JSON_pack_data_auto ("purse_sig",
521 : &pcm->purse_sig),
522 : GNUNET_JSON_pack_data_auto ("h_contract_terms",
523 : &pcm->h_contract_terms),
524 : GNUNET_JSON_pack_timestamp ("merge_timestamp",
525 : merge_timestamp),
526 : GNUNET_JSON_pack_timestamp ("purse_expiration",
527 : pcm->purse_expiration));
528 0 : GNUNET_assert (NULL != create_with_merge_obj);
529 0 : eh = TALER_EXCHANGE_curl_easy_get_ (pcm->url);
530 0 : if ( (NULL == eh) ||
531 : (GNUNET_OK !=
532 0 : TALER_curl_easy_post (&pcm->ctx,
533 : eh,
534 : create_with_merge_obj)) )
535 : {
536 0 : GNUNET_break (0);
537 0 : if (NULL != eh)
538 0 : curl_easy_cleanup (eh);
539 0 : json_decref (create_with_merge_obj);
540 0 : GNUNET_free (pcm->econtract.econtract);
541 0 : GNUNET_free (pcm->url);
542 0 : GNUNET_free (pcm);
543 0 : return NULL;
544 : }
545 0 : json_decref (create_with_merge_obj);
546 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
547 : "URL for purse create_with_merge: `%s'\n",
548 : pcm->url);
549 0 : ctx = TEAH_handle_to_context (exchange);
550 0 : pcm->job = GNUNET_CURL_job_add2 (ctx,
551 : eh,
552 0 : pcm->ctx.headers,
553 : &handle_purse_create_with_merge_finished,
554 : pcm);
555 0 : return pcm;
556 : }
557 :
558 :
559 : void
560 0 : TALER_EXCHANGE_purse_create_with_merge_cancel (
561 : struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm)
562 : {
563 0 : if (NULL != pcm->job)
564 : {
565 0 : GNUNET_CURL_job_cancel (pcm->job);
566 0 : pcm->job = NULL;
567 : }
568 0 : GNUNET_free (pcm->url);
569 0 : TALER_curl_easy_post_finished (&pcm->ctx);
570 0 : GNUNET_free (pcm->econtract.econtract);
571 0 : GNUNET_free (pcm);
572 0 : }
573 :
574 :
575 : /* end of exchange_api_purse_create_with_merge.c */
|