Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2017-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 Affero 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 Affero General Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-exchange-httpd_recoup.c
18 : * @brief Handle /recoup requests; parses the POST and JSON and
19 : * verifies the coin signature before handing things off
20 : * to the database.
21 : * @author Christian Grothoff
22 : */
23 : #include "taler/platform.h"
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <gnunet/gnunet_json_lib.h>
26 : #include <jansson.h>
27 : #include <microhttpd.h>
28 : #include <pthread.h>
29 : #include "taler/taler_json_lib.h"
30 : #include "taler/taler_mhd_lib.h"
31 : #include "taler-exchange-httpd_db.h"
32 : #include "taler-exchange-httpd_recoup.h"
33 : #include "taler-exchange-httpd_responses.h"
34 : #include "taler-exchange-httpd_keys.h"
35 : #include "taler/taler_exchangedb_lib.h"
36 :
37 : /**
38 : * Closure for #recoup_transaction.
39 : */
40 : struct RecoupContext
41 : {
42 : /**
43 : * Hash identifying the withdraw request.
44 : */
45 : struct TALER_BlindedCoinHashP h_coin_ev;
46 :
47 : /**
48 : * Set by #recoup_transaction() to the reserve that will
49 : * receive the recoup, if #refreshed is #GNUNET_NO.
50 : */
51 : struct TALER_ReservePublicKeyP reserve_pub;
52 :
53 : /**
54 : * Details about the coin.
55 : */
56 : const struct TALER_CoinPublicInfo *coin;
57 :
58 : /**
59 : * Key used to blind the coin.
60 : */
61 : const union GNUNET_CRYPTO_BlindingSecretP *coin_bks;
62 :
63 : /**
64 : * Signature of the coin requesting recoup.
65 : */
66 : const struct TALER_CoinSpendSignatureP *coin_sig;
67 :
68 : /**
69 : * Unique ID of the withdraw operation in the withdraw table.
70 : */
71 : uint64_t withdraw_serial_id;
72 :
73 : /**
74 : * Unique ID of the coin in the known_coins table.
75 : */
76 : uint64_t known_coin_id;
77 :
78 : /**
79 : * Set by #recoup_transaction to the timestamp when the recoup
80 : * was accepted.
81 : */
82 : struct GNUNET_TIME_Timestamp now;
83 :
84 : };
85 :
86 :
87 : /**
88 : * Execute a "recoup". The validity of the coin and signature have
89 : * already been checked. The database must now check that the coin is
90 : * not (double) spent, and execute the transaction.
91 : *
92 : * IF it returns a non-error code, the transaction logic MUST
93 : * NOT queue a MHD response. IF it returns an hard error, the
94 : * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
95 : * it returns the soft error code, the function MAY be called again to
96 : * retry and MUST not queue a MHD response.
97 : *
98 : * @param cls the `struct RecoupContext *`
99 : * @param connection MHD request which triggered the transaction
100 : * @param[out] mhd_ret set to MHD response status for @a connection,
101 : * if transaction failed (!)
102 : * @return transaction status code
103 : */
104 : static enum GNUNET_DB_QueryStatus
105 0 : recoup_transaction (void *cls,
106 : struct MHD_Connection *connection,
107 : MHD_RESULT *mhd_ret)
108 : {
109 0 : struct RecoupContext *pc = cls;
110 : enum GNUNET_DB_QueryStatus qs;
111 : bool recoup_ok;
112 : bool internal_failure;
113 :
114 : /* Finally, store new refund data */
115 0 : pc->now = GNUNET_TIME_timestamp_get ();
116 0 : qs = TEH_plugin->do_recoup (TEH_plugin->cls,
117 0 : &pc->reserve_pub,
118 : pc->withdraw_serial_id,
119 : pc->coin_bks,
120 0 : &pc->coin->coin_pub,
121 : pc->known_coin_id,
122 : pc->coin_sig,
123 : &pc->now,
124 : &recoup_ok,
125 : &internal_failure);
126 0 : if (0 > qs)
127 : {
128 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
129 0 : *mhd_ret = TALER_MHD_reply_with_error (
130 : connection,
131 : MHD_HTTP_INTERNAL_SERVER_ERROR,
132 : TALER_EC_GENERIC_DB_FETCH_FAILED,
133 : "do_recoup");
134 0 : return qs;
135 : }
136 :
137 0 : if (internal_failure)
138 : {
139 0 : GNUNET_break (0);
140 0 : *mhd_ret = TALER_MHD_reply_with_error (
141 : connection,
142 : MHD_HTTP_INTERNAL_SERVER_ERROR,
143 : TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
144 : "do_recoup");
145 0 : return GNUNET_DB_STATUS_HARD_ERROR;
146 : }
147 0 : if (! recoup_ok)
148 : {
149 0 : *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
150 : connection,
151 : TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
152 0 : &pc->coin->denom_pub_hash,
153 0 : &pc->coin->coin_pub);
154 0 : return GNUNET_DB_STATUS_HARD_ERROR;
155 : }
156 0 : return qs;
157 : }
158 :
159 :
160 : /**
161 : * We have parsed the JSON information about the recoup request. Do
162 : * some basic sanity checks (especially that the signature on the
163 : * request and coin is valid) and then execute the recoup operation.
164 : * Note that we need the DB to check the fee structure, so this is not
165 : * done here but during the recoup_transaction().
166 : *
167 : * @param connection the MHD connection to handle
168 : * @param coin information about the coin
169 : * @param exchange_vals values contributed by the exchange
170 : * during withdrawal
171 : * @param coin_bks blinding data of the coin (to be checked)
172 : * @param h_planchets The hash of the commitment of the original withdraw request
173 : * @param nonce coin's nonce if CS is used
174 : * @param coin_sig signature of the coin
175 : * @return MHD result code
176 : */
177 : static MHD_RESULT
178 0 : verify_and_execute_recoup (
179 : struct MHD_Connection *connection,
180 : const struct TALER_CoinPublicInfo *coin,
181 : const struct TALER_ExchangeBlindingValues *exchange_vals,
182 : const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
183 : const struct TALER_HashBlindedPlanchetsP *h_planchets,
184 : const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
185 : const struct TALER_CoinSpendSignatureP *coin_sig)
186 : {
187 : struct RecoupContext pc;
188 : const struct TEH_DenominationKey *dk;
189 : MHD_RESULT mret;
190 :
191 : /* check denomination exists and is in recoup mode */
192 0 : dk = TEH_keys_denomination_by_hash (&coin->denom_pub_hash,
193 : connection,
194 : &mret);
195 0 : if (NULL == dk)
196 0 : return mret;
197 0 : if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
198 : {
199 : /* This denomination is past the expiration time for recoup */
200 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
201 : connection,
202 : &coin->denom_pub_hash,
203 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
204 : "RECOUP");
205 : }
206 0 : if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
207 : {
208 : /* This denomination is not yet valid */
209 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
210 : connection,
211 : &coin->denom_pub_hash,
212 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
213 : "RECOUP");
214 : }
215 0 : if (! dk->recoup_possible)
216 : {
217 : /* This denomination is not eligible for recoup */
218 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
219 : connection,
220 : &coin->denom_pub_hash,
221 : TALER_EC_EXCHANGE_RECOUP_NOT_ELIGIBLE,
222 : "RECOUP");
223 : }
224 :
225 : /* check denomination signature */
226 0 : switch (dk->denom_pub.bsign_pub_key->cipher)
227 : {
228 0 : case GNUNET_CRYPTO_BSA_RSA:
229 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
230 0 : break;
231 0 : case GNUNET_CRYPTO_BSA_CS:
232 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
233 0 : break;
234 0 : default:
235 0 : break;
236 : }
237 0 : if (GNUNET_YES !=
238 0 : TALER_test_coin_valid (coin,
239 : &dk->denom_pub))
240 : {
241 0 : GNUNET_break_op (0);
242 0 : return TALER_MHD_reply_with_error (
243 : connection,
244 : MHD_HTTP_FORBIDDEN,
245 : TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
246 : NULL);
247 : }
248 :
249 : /* check recoup request signature */
250 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
251 0 : if (GNUNET_OK !=
252 0 : TALER_wallet_recoup_verify (&coin->denom_pub_hash,
253 : coin_bks,
254 : &coin->coin_pub,
255 : coin_sig))
256 : {
257 0 : GNUNET_break_op (0);
258 0 : return TALER_MHD_reply_with_error (
259 : connection,
260 : MHD_HTTP_FORBIDDEN,
261 : TALER_EC_EXCHANGE_RECOUP_SIGNATURE_INVALID,
262 : NULL);
263 : }
264 :
265 : /* re-compute client-side blinding so we can
266 : (a bit later) check that this coin was indeed
267 : signed by us. */
268 : {
269 : struct TALER_CoinPubHashP c_hash;
270 : struct TALER_BlindedPlanchet blinded_planchet;
271 :
272 0 : if (GNUNET_OK !=
273 0 : TALER_denom_blind (&dk->denom_pub,
274 : coin_bks,
275 : nonce,
276 : &coin->h_age_commitment,
277 : &coin->coin_pub,
278 : exchange_vals,
279 : &c_hash,
280 : &blinded_planchet))
281 : {
282 0 : GNUNET_break (0);
283 0 : return TALER_MHD_reply_with_error (
284 : connection,
285 : MHD_HTTP_INTERNAL_SERVER_ERROR,
286 : TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED,
287 : NULL);
288 : }
289 0 : TALER_coin_ev_hash (&blinded_planchet,
290 : &coin->denom_pub_hash,
291 : &pc.h_coin_ev);
292 0 : TALER_blinded_planchet_free (&blinded_planchet);
293 : }
294 :
295 0 : pc.coin_sig = coin_sig;
296 0 : pc.coin_bks = coin_bks;
297 0 : pc.coin = coin;
298 :
299 : {
300 0 : MHD_RESULT mhd_ret = MHD_NO;
301 : enum GNUNET_DB_QueryStatus qs;
302 :
303 : /* make sure coin is 'known' in database */
304 0 : qs = TEH_make_coin_known (coin,
305 : connection,
306 : &pc.known_coin_id,
307 : &mhd_ret);
308 : /* no transaction => no serialization failures should be possible */
309 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
310 0 : if (qs < 0)
311 0 : return mhd_ret;
312 : }
313 :
314 : {
315 : enum GNUNET_DB_QueryStatus qs;
316 :
317 0 : qs = TEH_plugin->get_reserve_by_h_planchets (
318 0 : TEH_plugin->cls,
319 : h_planchets,
320 : &pc.reserve_pub,
321 : &pc.withdraw_serial_id);
322 0 : if (0 > qs)
323 : {
324 0 : GNUNET_break (0);
325 0 : return TALER_MHD_reply_with_error (
326 : connection,
327 : MHD_HTTP_INTERNAL_SERVER_ERROR,
328 : TALER_EC_GENERIC_DB_FETCH_FAILED,
329 : "get_reserve_by_commitment");
330 : }
331 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
332 : {
333 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
334 : "Recoup requested for unknown envelope %s\n",
335 : GNUNET_h2s (&pc.h_coin_ev.hash));
336 0 : return TALER_MHD_reply_with_error (
337 : connection,
338 : MHD_HTTP_NOT_FOUND,
339 : TALER_EC_EXCHANGE_RECOUP_WITHDRAW_NOT_FOUND,
340 : NULL);
341 : }
342 : }
343 :
344 : /* Perform actual recoup transaction */
345 : {
346 : MHD_RESULT mhd_ret;
347 :
348 0 : if (GNUNET_OK !=
349 0 : TEH_DB_run_transaction (connection,
350 : "run recoup",
351 : TEH_MT_REQUEST_OTHER,
352 : &mhd_ret,
353 : &recoup_transaction,
354 : &pc))
355 0 : return mhd_ret;
356 : }
357 : /* Recoup succeeded, return result */
358 0 : return TALER_MHD_REPLY_JSON_PACK (connection,
359 : MHD_HTTP_OK,
360 : GNUNET_JSON_pack_data_auto (
361 : "reserve_pub",
362 : &pc.reserve_pub));
363 : }
364 :
365 :
366 : /**
367 : * Handle a "/coins/$COIN_PUB/recoup" request. Parses the JSON, and, if
368 : * successful, passes the JSON data to #verify_and_execute_recoup() to further
369 : * check the details of the operation specified. If everything checks out,
370 : * this will ultimately lead to the refund being executed, or rejected.
371 : *
372 : * @param connection the MHD connection to handle
373 : * @param coin_pub public key of the coin
374 : * @param root uploaded JSON data
375 : * @return MHD result code
376 : */
377 : MHD_RESULT
378 0 : TEH_handler_recoup (struct MHD_Connection *connection,
379 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
380 : const json_t *root)
381 : {
382 : enum GNUNET_GenericReturnValue ret;
383 : struct TALER_CoinPublicInfo coin;
384 : union GNUNET_CRYPTO_BlindingSecretP coin_bks;
385 : struct TALER_CoinSpendSignatureP coin_sig;
386 : struct TALER_ExchangeBlindingValues exchange_vals;
387 : struct TALER_HashBlindedPlanchetsP h_planchets;
388 : union GNUNET_CRYPTO_BlindSessionNonce nonce;
389 : bool no_nonce;
390 : struct GNUNET_JSON_Specification spec[] = {
391 0 : GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
392 : &coin.denom_pub_hash),
393 0 : TALER_JSON_spec_denom_sig ("denom_sig",
394 : &coin.denom_sig),
395 0 : GNUNET_JSON_spec_fixed_auto ("h_planchets",
396 : &h_planchets),
397 0 : TALER_JSON_spec_exchange_blinding_values ("ewv",
398 : &exchange_vals),
399 0 : GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret",
400 : &coin_bks),
401 0 : GNUNET_JSON_spec_fixed_auto ("coin_sig",
402 : &coin_sig),
403 0 : GNUNET_JSON_spec_mark_optional (
404 : GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
405 : &coin.h_age_commitment),
406 : &coin.no_age_commitment),
407 0 : GNUNET_JSON_spec_mark_optional (
408 : GNUNET_JSON_spec_fixed_auto ("nonce",
409 : &nonce),
410 : &no_nonce),
411 0 : GNUNET_JSON_spec_end ()
412 : };
413 :
414 0 : memset (&coin,
415 : 0,
416 : sizeof (coin));
417 0 : coin.coin_pub = *coin_pub;
418 0 : ret = TALER_MHD_parse_json_data (connection,
419 : root,
420 : spec);
421 0 : if (GNUNET_SYSERR == ret)
422 0 : return MHD_NO; /* hard failure */
423 0 : if (GNUNET_NO == ret)
424 0 : return MHD_YES; /* failure */
425 : {
426 : MHD_RESULT res;
427 :
428 0 : res = verify_and_execute_recoup (connection,
429 : &coin,
430 : &exchange_vals,
431 : &coin_bks,
432 : &h_planchets,
433 : no_nonce
434 : ? NULL
435 : : &nonce,
436 : &coin_sig);
437 0 : GNUNET_JSON_parse_free (spec);
438 0 : return res;
439 : }
440 : }
441 :
442 :
443 : /* end of taler-exchange-httpd_recoup.c */
|