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_merge.c
19 : * @brief Implementation of the client to merge a purse
20 : * into 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 merge with deposit handle
39 : */
40 : struct TALER_EXCHANGE_AccountMergeHandle
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_AccountMergeCallback cb;
68 :
69 : /**
70 : * Closure for @a cb.
71 : */
72 : void *cb_cls;
73 :
74 : /**
75 : * Base URL of the provider hosting the @e reserve_pub.
76 : */
77 : char *provider_url;
78 :
79 : /**
80 : * Signature for our operation.
81 : */
82 : struct TALER_PurseMergeSignatureP merge_sig;
83 :
84 : /**
85 : * Expected value in the purse after fees.
86 : */
87 : struct TALER_Amount purse_value_after_fees;
88 :
89 : /**
90 : * Public key of the reserve public key.
91 : */
92 : struct TALER_ReservePublicKeyP reserve_pub;
93 :
94 : /**
95 : * Public key of the purse.
96 : */
97 : struct TALER_PurseContractPublicKeyP purse_pub;
98 :
99 : /**
100 : * Hash over the purse's contrac terms.
101 : */
102 : struct TALER_PrivateContractHashP h_contract_terms;
103 :
104 : /**
105 : * When does the purse expire.
106 : */
107 : struct GNUNET_TIME_Timestamp purse_expiration;
108 :
109 : /**
110 : * Our merge key.
111 : */
112 : struct TALER_PurseMergePrivateKeyP merge_priv;
113 :
114 : /**
115 : * Reserve signature affirming the merge.
116 : */
117 : struct TALER_ReserveSignatureP reserve_sig;
118 :
119 : };
120 :
121 :
122 : /**
123 : * Function called when we're done processing the
124 : * HTTP /purse/$PID/merge request.
125 : *
126 : * @param cls the `struct TALER_EXCHANGE_AccountMergeHandle`
127 : * @param response_code HTTP response code, 0 on error
128 : * @param response parsed JSON result, NULL on error
129 : */
130 : static void
131 0 : handle_purse_merge_finished (void *cls,
132 : long response_code,
133 : const void *response)
134 : {
135 0 : struct TALER_EXCHANGE_AccountMergeHandle *pch = cls;
136 0 : const json_t *j = response;
137 0 : struct TALER_EXCHANGE_AccountMergeResponse dr = {
138 : .hr.reply = j,
139 0 : .hr.http_status = (unsigned int) response_code,
140 0 : .reserve_sig = &pch->reserve_sig
141 : };
142 :
143 0 : pch->job = NULL;
144 0 : switch (response_code)
145 : {
146 0 : case 0:
147 0 : dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
148 0 : break;
149 0 : case MHD_HTTP_OK:
150 : {
151 : const struct TALER_EXCHANGE_Keys *key_state;
152 : struct TALER_Amount total_deposited;
153 : struct GNUNET_JSON_Specification spec[] = {
154 0 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
155 : &dr.details.success.exchange_sig),
156 0 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
157 : &dr.details.success.exchange_pub),
158 0 : GNUNET_JSON_spec_timestamp ("exchange_timestamp",
159 : &dr.details.success.etime),
160 0 : TALER_JSON_spec_amount ("merge_amount",
161 0 : pch->purse_value_after_fees.currency,
162 : &total_deposited),
163 0 : GNUNET_JSON_spec_end ()
164 : };
165 :
166 0 : if (GNUNET_OK !=
167 0 : GNUNET_JSON_parse (j,
168 : spec,
169 : NULL, NULL))
170 : {
171 0 : GNUNET_break_op (0);
172 0 : dr.hr.http_status = 0;
173 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
174 0 : break;
175 : }
176 0 : key_state = TALER_EXCHANGE_get_keys (pch->exchange);
177 0 : if (GNUNET_OK !=
178 0 : TALER_EXCHANGE_test_signing_key (key_state,
179 : &dr.details.success.exchange_pub))
180 : {
181 0 : GNUNET_break_op (0);
182 0 : dr.hr.http_status = 0;
183 0 : dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID;
184 0 : break;
185 : }
186 0 : if (GNUNET_OK !=
187 0 : TALER_exchange_online_purse_merged_verify (
188 : dr.details.success.etime,
189 : pch->purse_expiration,
190 0 : &pch->purse_value_after_fees,
191 0 : &pch->purse_pub,
192 0 : &pch->h_contract_terms,
193 0 : &pch->reserve_pub,
194 0 : pch->provider_url,
195 : &dr.details.success.exchange_pub,
196 : &dr.details.success.exchange_sig))
197 : {
198 0 : GNUNET_break_op (0);
199 0 : dr.hr.http_status = 0;
200 0 : dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID;
201 0 : break;
202 : }
203 : }
204 0 : break;
205 0 : case MHD_HTTP_BAD_REQUEST:
206 : /* This should never happen, either us or the exchange is buggy
207 : (or API version conflict); just pass JSON reply to the application */
208 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
209 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
210 0 : break;
211 0 : case MHD_HTTP_PAYMENT_REQUIRED:
212 : /* purse was not (yet) full */
213 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
214 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
215 0 : break;
216 0 : case MHD_HTTP_FORBIDDEN:
217 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
218 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
219 : /* Nothing really to verify, exchange says one of the signatures is
220 : invalid; as we checked them, this should never happen, we
221 : should pass the JSON reply to the application */
222 0 : break;
223 0 : case MHD_HTTP_NOT_FOUND:
224 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
225 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
226 : /* Nothing really to verify, this should never
227 : happen, we should pass the JSON reply to the application */
228 0 : break;
229 0 : case MHD_HTTP_CONFLICT:
230 : {
231 : struct TALER_PurseMergePublicKeyP merge_pub;
232 :
233 0 : GNUNET_CRYPTO_eddsa_key_get_public (&pch->merge_priv.eddsa_priv,
234 : &merge_pub.eddsa_pub);
235 0 : if (GNUNET_OK !=
236 0 : TALER_EXCHANGE_check_purse_merge_conflict_ (
237 0 : &pch->merge_sig,
238 : &merge_pub,
239 0 : &pch->purse_pub,
240 0 : pch->provider_url,
241 : j))
242 : {
243 0 : GNUNET_break_op (0);
244 0 : dr.hr.http_status = 0;
245 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
246 0 : break;
247 : }
248 0 : break;
249 : }
250 : break;
251 0 : case MHD_HTTP_GONE:
252 : /* could happen if denomination was revoked */
253 : /* Note: one might want to check /keys for revocation
254 : signature here, alas tricky in case our /keys
255 : is outdated => left to clients */
256 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
257 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
258 0 : break;
259 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
260 : {
261 : struct GNUNET_JSON_Specification spec[] = {
262 0 : GNUNET_JSON_spec_uint64 (
263 : "requirement_row",
264 : &dr.details.unavailable_for_legal_reasons.requirement_row),
265 0 : GNUNET_JSON_spec_end ()
266 : };
267 :
268 0 : if (GNUNET_OK !=
269 0 : GNUNET_JSON_parse (j,
270 : spec,
271 : NULL, NULL))
272 : {
273 0 : GNUNET_break_op (0);
274 0 : dr.hr.http_status = 0;
275 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
276 0 : break;
277 : }
278 : }
279 0 : break;
280 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
281 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
282 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
283 : /* Server had an internal issue; we should retry, but this API
284 : leaves this to the application */
285 0 : break;
286 0 : default:
287 : /* unexpected response code */
288 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
289 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
290 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
291 : "Unexpected response code %u/%d for exchange deposit\n",
292 : (unsigned int) response_code,
293 : dr.hr.ec);
294 0 : GNUNET_break_op (0);
295 0 : break;
296 : }
297 0 : pch->cb (pch->cb_cls,
298 : &dr);
299 0 : TALER_EXCHANGE_account_merge_cancel (pch);
300 0 : }
301 :
302 :
303 : struct TALER_EXCHANGE_AccountMergeHandle *
304 0 : TALER_EXCHANGE_account_merge (
305 : struct TALER_EXCHANGE_Handle *exchange,
306 : const char *reserve_exchange_url,
307 : const struct TALER_ReservePrivateKeyP *reserve_priv,
308 : const struct TALER_PurseContractPublicKeyP *purse_pub,
309 : const struct TALER_PurseMergePrivateKeyP *merge_priv,
310 : const struct TALER_PrivateContractHashP *h_contract_terms,
311 : uint8_t min_age,
312 : const struct TALER_Amount *purse_value_after_fees,
313 : struct GNUNET_TIME_Timestamp purse_expiration,
314 : struct GNUNET_TIME_Timestamp merge_timestamp,
315 : TALER_EXCHANGE_AccountMergeCallback cb,
316 : void *cb_cls)
317 : {
318 : struct TALER_EXCHANGE_AccountMergeHandle *pch;
319 : struct GNUNET_CURL_Context *ctx;
320 : json_t *merge_obj;
321 : CURL *eh;
322 : char arg_str[sizeof (pch->purse_pub) * 2 + 32];
323 : char *reserve_url;
324 :
325 0 : pch = GNUNET_new (struct TALER_EXCHANGE_AccountMergeHandle);
326 0 : pch->exchange = exchange;
327 0 : pch->merge_priv = *merge_priv;
328 0 : pch->cb = cb;
329 0 : pch->cb_cls = cb_cls;
330 0 : pch->purse_pub = *purse_pub;
331 0 : pch->h_contract_terms = *h_contract_terms;
332 0 : pch->purse_expiration = purse_expiration;
333 0 : pch->purse_value_after_fees = *purse_value_after_fees;
334 0 : if (NULL == reserve_exchange_url)
335 0 : pch->provider_url = GNUNET_strdup (exchange->url);
336 : else
337 0 : pch->provider_url = GNUNET_strdup (reserve_exchange_url);
338 0 : GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
339 : &pch->reserve_pub.eddsa_pub);
340 :
341 0 : GNUNET_assert (GNUNET_YES ==
342 : TEAH_handle_is_ready (exchange));
343 : {
344 : char pub_str[sizeof (*purse_pub) * 2];
345 : char *end;
346 :
347 0 : end = GNUNET_STRINGS_data_to_string (
348 : purse_pub,
349 : sizeof (*purse_pub),
350 : pub_str,
351 : sizeof (pub_str));
352 0 : *end = '\0';
353 0 : GNUNET_snprintf (arg_str,
354 : sizeof (arg_str),
355 : "/purses/%s/merge",
356 : pub_str);
357 : }
358 0 : reserve_url = TALER_reserve_make_payto (pch->provider_url,
359 0 : &pch->reserve_pub);
360 0 : if (NULL == reserve_url)
361 : {
362 0 : GNUNET_break (0);
363 0 : GNUNET_free (pch->provider_url);
364 0 : GNUNET_free (pch);
365 0 : return NULL;
366 : }
367 0 : pch->url = TEAH_path_to_url (exchange,
368 : arg_str);
369 0 : if (NULL == pch->url)
370 : {
371 0 : GNUNET_break (0);
372 0 : GNUNET_free (reserve_url);
373 0 : GNUNET_free (pch->provider_url);
374 0 : GNUNET_free (pch);
375 0 : return NULL;
376 : }
377 0 : TALER_wallet_purse_merge_sign (reserve_url,
378 : merge_timestamp,
379 : purse_pub,
380 : merge_priv,
381 : &pch->merge_sig);
382 : {
383 : struct TALER_Amount zero_purse_fee;
384 :
385 0 : GNUNET_assert (GNUNET_OK ==
386 : TALER_amount_set_zero (purse_value_after_fees->currency,
387 : &zero_purse_fee));
388 0 : TALER_wallet_account_merge_sign (merge_timestamp,
389 : purse_pub,
390 : purse_expiration,
391 : h_contract_terms,
392 : purse_value_after_fees,
393 : &zero_purse_fee,
394 : min_age,
395 : TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
396 : reserve_priv,
397 : &pch->reserve_sig);
398 : }
399 0 : merge_obj = GNUNET_JSON_PACK (
400 : GNUNET_JSON_pack_string ("payto_uri",
401 : reserve_url),
402 : GNUNET_JSON_pack_data_auto ("merge_sig",
403 : &pch->merge_sig),
404 : GNUNET_JSON_pack_data_auto ("reserve_sig",
405 : &pch->reserve_sig),
406 : GNUNET_JSON_pack_timestamp ("merge_timestamp",
407 : merge_timestamp));
408 0 : GNUNET_assert (NULL != merge_obj);
409 0 : eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
410 0 : if ( (NULL == eh) ||
411 : (GNUNET_OK !=
412 0 : TALER_curl_easy_post (&pch->ctx,
413 : eh,
414 : merge_obj)) )
415 : {
416 0 : GNUNET_break (0);
417 0 : if (NULL != eh)
418 0 : curl_easy_cleanup (eh);
419 0 : json_decref (merge_obj);
420 0 : GNUNET_free (pch->provider_url);
421 0 : GNUNET_free (pch->url);
422 0 : GNUNET_free (pch);
423 0 : return NULL;
424 : }
425 0 : json_decref (merge_obj);
426 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
427 : "URL for purse merge: `%s'\n",
428 : pch->url);
429 0 : ctx = TEAH_handle_to_context (exchange);
430 0 : pch->job = GNUNET_CURL_job_add2 (ctx,
431 : eh,
432 0 : pch->ctx.headers,
433 : &handle_purse_merge_finished,
434 : pch);
435 0 : return pch;
436 : }
437 :
438 :
439 : void
440 0 : TALER_EXCHANGE_account_merge_cancel (
441 : struct TALER_EXCHANGE_AccountMergeHandle *pch)
442 : {
443 0 : if (NULL != pch->job)
444 : {
445 0 : GNUNET_CURL_job_cancel (pch->job);
446 0 : pch->job = NULL;
447 : }
448 0 : GNUNET_free (pch->url);
449 0 : GNUNET_free (pch->provider_url);
450 0 : TALER_curl_easy_post_finished (&pch->ctx);
451 0 : GNUNET_free (pch);
452 0 : }
453 :
454 :
455 : /* end of exchange_api_purse_merge.c */
|