Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2015-2021 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_link.c
19 : * @brief Implementation of the /coins/$COIN_PUB/link request
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <microhttpd.h> /* just for HTTP status codes */
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <gnunet/gnunet_curl_lib.h>
26 : #include "taler_exchange_service.h"
27 : #include "taler_json_lib.h"
28 : #include "exchange_api_handle.h"
29 : #include "taler_signatures.h"
30 : #include "exchange_api_curl_defaults.h"
31 :
32 :
33 : /**
34 : * @brief A /coins/$COIN_PUB/link Handle
35 : */
36 : struct TALER_EXCHANGE_LinkHandle
37 : {
38 :
39 : /**
40 : * The connection to exchange this request handle will use
41 : */
42 : struct TALER_EXCHANGE_Handle *exchange;
43 :
44 : /**
45 : * The url for this request.
46 : */
47 : char *url;
48 :
49 : /**
50 : * Handle for the request.
51 : */
52 : struct GNUNET_CURL_Job *job;
53 :
54 : /**
55 : * Function to call with the result.
56 : */
57 : TALER_EXCHANGE_LinkCallback link_cb;
58 :
59 : /**
60 : * Closure for @e cb.
61 : */
62 : void *link_cb_cls;
63 :
64 : /**
65 : * Private key of the coin, required to decode link information.
66 : */
67 : struct TALER_CoinSpendPrivateKeyP coin_priv;
68 :
69 : /**
70 : * Age commitment and proof of the original coin, might be NULL.
71 : * Required to derive the new age commitment and proof.
72 : */
73 : const struct TALER_AgeCommitmentProof *age_commitment_proof;
74 :
75 : };
76 :
77 :
78 : /**
79 : * Parse the provided linkage data from the "200 OK" response
80 : * for one of the coins.
81 : *
82 : * @param lh link handle
83 : * @param json json reply with the data for one coin
84 : * @param trans_pub our transfer public key
85 : * @param[out] lci where to return coin details
86 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
87 : */
88 : static enum GNUNET_GenericReturnValue
89 0 : parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
90 : const json_t *json,
91 : const struct TALER_TransferPublicKeyP *trans_pub,
92 : struct TALER_EXCHANGE_LinkedCoinInfo *lci)
93 : {
94 : struct TALER_BlindedDenominationSignature bsig;
95 : struct TALER_DenominationPublicKey rpub;
96 : struct TALER_CoinSpendSignatureP link_sig;
97 : union TALER_DenominationBlindingKeyP bks;
98 : struct TALER_ExchangeWithdrawValues alg_values;
99 : struct TALER_CsNonce nonce;
100 : bool no_nonce;
101 : uint32_t coin_idx;
102 : struct GNUNET_JSON_Specification spec[] = {
103 0 : TALER_JSON_spec_denom_pub ("denom_pub",
104 : &rpub),
105 0 : TALER_JSON_spec_blinded_denom_sig ("ev_sig",
106 : &bsig),
107 0 : TALER_JSON_spec_exchange_withdraw_values ("ewv",
108 : &alg_values),
109 0 : GNUNET_JSON_spec_fixed_auto ("link_sig",
110 : &link_sig),
111 0 : GNUNET_JSON_spec_uint32 ("coin_idx",
112 : &coin_idx),
113 0 : GNUNET_JSON_spec_mark_optional (
114 : GNUNET_JSON_spec_fixed_auto ("cs_nonce",
115 : &nonce),
116 : &no_nonce),
117 0 : GNUNET_JSON_spec_end ()
118 : };
119 : struct TALER_TransferSecretP secret;
120 : struct TALER_PlanchetDetail pd;
121 : struct TALER_CoinPubHashP c_hash;
122 :
123 : /* parse reply */
124 0 : if (GNUNET_OK !=
125 0 : GNUNET_JSON_parse (json,
126 : spec,
127 : NULL, NULL))
128 : {
129 0 : GNUNET_break_op (0);
130 0 : return GNUNET_SYSERR;
131 : }
132 0 : TALER_link_recover_transfer_secret (trans_pub,
133 : &lh->coin_priv,
134 : &secret);
135 0 : TALER_transfer_secret_to_planchet_secret (&secret,
136 : coin_idx,
137 : &lci->ps);
138 0 : TALER_planchet_setup_coin_priv (&lci->ps,
139 : &alg_values,
140 : &lci->coin_priv);
141 0 : TALER_planchet_blinding_secret_create (&lci->ps,
142 : &alg_values,
143 : &bks);
144 :
145 0 : lci->age_commitment_proof = NULL;
146 0 : lci->h_age_commitment = NULL;
147 :
148 : /* Derive the age commitment and calculate the hash */
149 0 : if (NULL != lh->age_commitment_proof)
150 : {
151 0 : lci->age_commitment_proof = GNUNET_new (struct TALER_AgeCommitmentProof);
152 0 : lci->h_age_commitment = GNUNET_new (struct TALER_AgeCommitmentHash);
153 :
154 0 : GNUNET_assert (GNUNET_OK ==
155 : TALER_age_commitment_derive (
156 : lh->age_commitment_proof,
157 : &secret.key,
158 : lci->age_commitment_proof));
159 :
160 0 : TALER_age_commitment_hash (
161 0 : &(lci->age_commitment_proof->commitment),
162 : lci->h_age_commitment);
163 : }
164 :
165 0 : if (GNUNET_OK !=
166 0 : TALER_planchet_prepare (&rpub,
167 : &alg_values,
168 : &bks,
169 0 : &lci->coin_priv,
170 0 : lci->h_age_commitment,
171 : &c_hash,
172 : &pd))
173 : {
174 0 : GNUNET_break (0);
175 0 : GNUNET_JSON_parse_free (spec);
176 0 : return GNUNET_SYSERR;
177 : }
178 0 : if (TALER_DENOMINATION_CS == alg_values.cipher)
179 : {
180 0 : if (no_nonce)
181 : {
182 0 : GNUNET_break_op (0);
183 0 : GNUNET_JSON_parse_free (spec);
184 0 : return GNUNET_SYSERR;
185 : }
186 0 : pd.blinded_planchet.details.cs_blinded_planchet.nonce = nonce;
187 : }
188 : /* extract coin and signature */
189 0 : if (GNUNET_OK !=
190 0 : TALER_denom_sig_unblind (&lci->sig,
191 : &bsig,
192 : &bks,
193 : &c_hash,
194 : &alg_values,
195 : &rpub))
196 : {
197 0 : GNUNET_break_op (0);
198 0 : return GNUNET_SYSERR;
199 : }
200 : /* verify link_sig */
201 : {
202 : struct TALER_CoinSpendPublicKeyP old_coin_pub;
203 : struct TALER_BlindedCoinHashP coin_envelope_hash;
204 :
205 0 : GNUNET_CRYPTO_eddsa_key_get_public (&lh->coin_priv.eddsa_priv,
206 : &old_coin_pub.eddsa_pub);
207 :
208 0 : TALER_coin_ev_hash (&pd.blinded_planchet,
209 : &pd.denom_pub_hash,
210 : &coin_envelope_hash);
211 0 : if (GNUNET_OK !=
212 0 : TALER_wallet_link_verify (&pd.denom_pub_hash,
213 : trans_pub,
214 : &coin_envelope_hash,
215 : &old_coin_pub,
216 : &link_sig))
217 : {
218 0 : GNUNET_break_op (0);
219 0 : TALER_blinded_planchet_free (&pd.blinded_planchet);
220 0 : GNUNET_JSON_parse_free (spec);
221 0 : return GNUNET_SYSERR;
222 : }
223 0 : TALER_blinded_planchet_free (&pd.blinded_planchet);
224 : }
225 :
226 : /* clean up */
227 0 : TALER_denom_pub_deep_copy (&lci->pub,
228 : &rpub);
229 0 : GNUNET_JSON_parse_free (spec);
230 0 : return GNUNET_OK;
231 : }
232 :
233 :
234 : /**
235 : * Parse the provided linkage data from the "200 OK" response
236 : * for one of the coins.
237 : *
238 : * @param[in,out] lh link handle (callback may be zero'ed out)
239 : * @param json json reply with the data for one coin
240 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
241 : */
242 : static enum GNUNET_GenericReturnValue
243 0 : parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
244 : const json_t *json)
245 : {
246 : unsigned int session;
247 : unsigned int num_coins;
248 : int ret;
249 0 : struct TALER_EXCHANGE_LinkResult lr = {
250 : .hr.reply = json,
251 : .hr.http_status = MHD_HTTP_OK
252 : };
253 :
254 0 : if (! json_is_array (json))
255 : {
256 0 : GNUNET_break_op (0);
257 0 : return GNUNET_SYSERR;
258 : }
259 0 : num_coins = 0;
260 : /* Theoretically, a coin may have been melted repeatedly
261 : into different sessions; so the response is an array
262 : which contains information by melting session. That
263 : array contains another array. However, our API returns
264 : a single 1d array, so we flatten the 2d array that is
265 : returned into a single array. Note that usually a coin
266 : is melted at most once, and so we'll only run this
267 : loop once for 'session=0' in most cases.
268 :
269 : num_coins tracks the size of the 1d array we return,
270 : whilst 'i' and 'session' track the 2d array. *///
271 0 : for (session = 0; session<json_array_size (json); session++)
272 : {
273 : json_t *jsona;
274 : struct GNUNET_JSON_Specification spec[] = {
275 0 : GNUNET_JSON_spec_json ("new_coins", &jsona),
276 0 : GNUNET_JSON_spec_end ()
277 : };
278 :
279 0 : if (GNUNET_OK !=
280 0 : GNUNET_JSON_parse (json_array_get (json,
281 : session),
282 : spec,
283 : NULL, NULL))
284 : {
285 0 : GNUNET_break_op (0);
286 0 : return GNUNET_SYSERR;
287 : }
288 0 : if (! json_is_array (jsona))
289 : {
290 0 : GNUNET_break_op (0);
291 0 : GNUNET_JSON_parse_free (spec);
292 0 : return GNUNET_SYSERR;
293 : }
294 :
295 : /* count all coins over all sessions */
296 0 : num_coins += json_array_size (jsona);
297 0 : GNUNET_JSON_parse_free (spec);
298 : }
299 : /* Now that we know how big the 1d array is, allocate
300 : and fill it. */
301 0 : {
302 : unsigned int off_coin; /* index into 1d array */
303 : unsigned int i;
304 0 : struct TALER_EXCHANGE_LinkedCoinInfo lcis[GNUNET_NZL (num_coins)];
305 :
306 0 : memset (lcis, 0, sizeof (lcis));
307 0 : off_coin = 0;
308 0 : for (session = 0; session<json_array_size (json); session++)
309 : {
310 : json_t *jsona;
311 : struct TALER_TransferPublicKeyP trans_pub;
312 : struct GNUNET_JSON_Specification spec[] = {
313 0 : GNUNET_JSON_spec_json ("new_coins",
314 : &jsona),
315 0 : GNUNET_JSON_spec_fixed_auto ("transfer_pub",
316 : &trans_pub),
317 0 : GNUNET_JSON_spec_end ()
318 : };
319 :
320 0 : if (GNUNET_OK !=
321 0 : GNUNET_JSON_parse (json_array_get (json,
322 : session),
323 : spec,
324 : NULL, NULL))
325 : {
326 0 : GNUNET_break_op (0);
327 0 : return GNUNET_SYSERR;
328 : }
329 0 : if (! json_is_array (jsona))
330 : {
331 0 : GNUNET_break_op (0);
332 0 : GNUNET_JSON_parse_free (spec);
333 0 : return GNUNET_SYSERR;
334 : }
335 :
336 : /* decode all coins */
337 0 : for (i = 0; i<json_array_size (jsona); i++)
338 : {
339 : struct TALER_EXCHANGE_LinkedCoinInfo *lci;
340 :
341 0 : lci = &lcis[i + off_coin];
342 0 : GNUNET_assert (i + off_coin < num_coins);
343 0 : if (GNUNET_OK !=
344 0 : parse_link_coin (lh,
345 0 : json_array_get (jsona,
346 : i),
347 : &trans_pub,
348 : lci))
349 : {
350 0 : GNUNET_break_op (0);
351 0 : break;
352 : }
353 : }
354 : /* check if we really got all, then invoke callback */
355 0 : off_coin += i;
356 0 : if (i != json_array_size (jsona))
357 : {
358 0 : GNUNET_break_op (0);
359 0 : ret = GNUNET_SYSERR;
360 0 : GNUNET_JSON_parse_free (spec);
361 0 : break;
362 : }
363 0 : GNUNET_JSON_parse_free (spec);
364 : } /* end of for (session) */
365 :
366 0 : if (off_coin == num_coins)
367 : {
368 0 : lr.details.success.num_coins = num_coins;
369 0 : lr.details.success.coins = lcis;
370 0 : lh->link_cb (lh->link_cb_cls,
371 : &lr);
372 0 : lh->link_cb = NULL;
373 0 : ret = GNUNET_OK;
374 : }
375 : else
376 : {
377 0 : GNUNET_break_op (0);
378 0 : ret = GNUNET_SYSERR;
379 : }
380 :
381 : /* clean up */
382 0 : GNUNET_assert (off_coin <= num_coins);
383 0 : for (i = 0; i<off_coin; i++)
384 : {
385 0 : TALER_denom_sig_free (&lcis[i].sig);
386 0 : TALER_denom_pub_free (&lcis[i].pub);
387 : }
388 : }
389 0 : return ret;
390 : }
391 :
392 :
393 : /**
394 : * Function called when we're done processing the
395 : * HTTP /coins/$COIN_PUB/link request.
396 : *
397 : * @param cls the `struct TALER_EXCHANGE_LinkHandle`
398 : * @param response_code HTTP response code, 0 on error
399 : * @param response parsed JSON result, NULL on error
400 : */
401 : static void
402 0 : handle_link_finished (void *cls,
403 : long response_code,
404 : const void *response)
405 : {
406 0 : struct TALER_EXCHANGE_LinkHandle *lh = cls;
407 0 : const json_t *j = response;
408 0 : struct TALER_EXCHANGE_LinkResult lr = {
409 : .hr.reply = j,
410 0 : .hr.http_status = (unsigned int) response_code
411 : };
412 :
413 0 : lh->job = NULL;
414 0 : switch (response_code)
415 : {
416 0 : case 0:
417 0 : lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
418 0 : break;
419 0 : case MHD_HTTP_OK:
420 0 : if (GNUNET_OK !=
421 0 : parse_link_ok (lh,
422 : j))
423 : {
424 0 : GNUNET_break_op (0);
425 0 : lr.hr.http_status = 0;
426 0 : lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
427 0 : break;
428 : }
429 0 : GNUNET_assert (NULL == lh->link_cb);
430 0 : TALER_EXCHANGE_link_cancel (lh);
431 0 : return;
432 0 : case MHD_HTTP_BAD_REQUEST:
433 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
434 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
435 : /* This should never happen, either us or the exchange is buggy
436 : (or API version conflict); just pass JSON reply to the application */
437 0 : break;
438 0 : case MHD_HTTP_NOT_FOUND:
439 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
440 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
441 : /* Nothing really to verify, exchange says this coin was not melted; we
442 : should pass the JSON reply to the application */
443 0 : break;
444 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
445 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
446 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
447 : /* Server had an internal issue; we should retry, but this API
448 : leaves this to the application */
449 0 : break;
450 0 : default:
451 : /* unexpected response code */
452 0 : GNUNET_break_op (0);
453 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
454 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
455 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
456 : "Unexpected response code %u/%d for exchange link\n",
457 : (unsigned int) response_code,
458 : (int) lr.hr.ec);
459 0 : break;
460 : }
461 0 : if (NULL != lh->link_cb)
462 0 : lh->link_cb (lh->link_cb_cls,
463 : &lr);
464 0 : TALER_EXCHANGE_link_cancel (lh);
465 : }
466 :
467 :
468 : struct TALER_EXCHANGE_LinkHandle *
469 0 : TALER_EXCHANGE_link (struct TALER_EXCHANGE_Handle *exchange,
470 : const struct TALER_CoinSpendPrivateKeyP *coin_priv,
471 : const struct
472 : TALER_AgeCommitmentProof *age_commitment_proof,
473 : TALER_EXCHANGE_LinkCallback link_cb,
474 : void *link_cb_cls)
475 : {
476 : struct TALER_EXCHANGE_LinkHandle *lh;
477 : CURL *eh;
478 : struct GNUNET_CURL_Context *ctx;
479 : struct TALER_CoinSpendPublicKeyP coin_pub;
480 : char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
481 :
482 0 : if (GNUNET_YES !=
483 0 : TEAH_handle_is_ready (exchange))
484 : {
485 0 : GNUNET_break (0);
486 0 : return NULL;
487 : }
488 :
489 0 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
490 : &coin_pub.eddsa_pub);
491 : {
492 : char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
493 : char *end;
494 :
495 0 : end = GNUNET_STRINGS_data_to_string (
496 : &coin_pub,
497 : sizeof (struct TALER_CoinSpendPublicKeyP),
498 : pub_str,
499 : sizeof (pub_str));
500 0 : *end = '\0';
501 0 : GNUNET_snprintf (arg_str,
502 : sizeof (arg_str),
503 : "/coins/%s/link",
504 : pub_str);
505 : }
506 0 : lh = GNUNET_new (struct TALER_EXCHANGE_LinkHandle);
507 0 : lh->exchange = exchange;
508 0 : lh->link_cb = link_cb;
509 0 : lh->link_cb_cls = link_cb_cls;
510 0 : lh->coin_priv = *coin_priv;
511 0 : lh->age_commitment_proof = age_commitment_proof;
512 0 : lh->url = TEAH_path_to_url (exchange,
513 : arg_str);
514 0 : if (NULL == lh->url)
515 : {
516 0 : GNUNET_free (lh);
517 0 : return NULL;
518 : }
519 0 : eh = TALER_EXCHANGE_curl_easy_get_ (lh->url);
520 0 : if (NULL == eh)
521 : {
522 0 : GNUNET_break (0);
523 0 : GNUNET_free (lh->url);
524 0 : GNUNET_free (lh);
525 0 : return NULL;
526 : }
527 0 : ctx = TEAH_handle_to_context (exchange);
528 0 : lh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
529 : eh,
530 : &handle_link_finished,
531 : lh);
532 0 : return lh;
533 : }
534 :
535 :
536 : void
537 0 : TALER_EXCHANGE_link_cancel (struct TALER_EXCHANGE_LinkHandle *lh)
538 : {
539 0 : if (NULL != lh->job)
540 : {
541 0 : GNUNET_CURL_job_cancel (lh->job);
542 0 : lh->job = NULL;
543 : }
544 0 : GNUNET_free (lh->url);
545 0 : GNUNET_free (lh);
546 0 : }
547 :
548 :
549 : /* end of exchange_api_link.c */
|