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 "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_json_lib.h"
30 : #include "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_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 TALER_DenominationBlindingKeyP *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 reserves_out table.
70 : */
71 : uint64_t reserve_out_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->reserve_out_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 nonce coin's nonce if CS is used
173 : * @param coin_sig signature of the coin
174 : * @return MHD result code
175 : */
176 : static MHD_RESULT
177 0 : verify_and_execute_recoup (
178 : struct MHD_Connection *connection,
179 : const struct TALER_CoinPublicInfo *coin,
180 : const struct TALER_ExchangeWithdrawValues *exchange_vals,
181 : const union TALER_DenominationBlindingKeyP *coin_bks,
182 : const struct TALER_CsNonce *nonce,
183 : const struct TALER_CoinSpendSignatureP *coin_sig)
184 : {
185 : struct RecoupContext pc;
186 : const struct TEH_DenominationKey *dk;
187 : MHD_RESULT mret;
188 :
189 : /* check denomination exists and is in recoup mode */
190 0 : dk = TEH_keys_denomination_by_hash (&coin->denom_pub_hash,
191 : connection,
192 : &mret);
193 0 : if (NULL == dk)
194 0 : return mret;
195 0 : if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
196 : {
197 : /* This denomination is past the expiration time for recoup */
198 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
199 : connection,
200 : &coin->denom_pub_hash,
201 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
202 : "RECOUP");
203 : }
204 0 : if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
205 : {
206 : /* This denomination is not yet valid */
207 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
208 : connection,
209 : &coin->denom_pub_hash,
210 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
211 : "RECOUP");
212 : }
213 0 : if (! dk->recoup_possible)
214 : {
215 : /* This denomination is not eligible for recoup */
216 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
217 : connection,
218 : &coin->denom_pub_hash,
219 : TALER_EC_EXCHANGE_RECOUP_NOT_ELIGIBLE,
220 : "RECOUP");
221 : }
222 :
223 : /* check denomination signature */
224 0 : switch (dk->denom_pub.cipher)
225 : {
226 0 : case TALER_DENOMINATION_RSA:
227 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
228 0 : break;
229 0 : case TALER_DENOMINATION_CS:
230 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
231 0 : break;
232 0 : default:
233 0 : break;
234 : }
235 0 : if (GNUNET_YES !=
236 0 : TALER_test_coin_valid (coin,
237 : &dk->denom_pub))
238 : {
239 0 : GNUNET_break_op (0);
240 0 : return TALER_MHD_reply_with_error (
241 : connection,
242 : MHD_HTTP_FORBIDDEN,
243 : TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
244 : NULL);
245 : }
246 :
247 : /* check recoup request signature */
248 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
249 0 : if (GNUNET_OK !=
250 0 : TALER_wallet_recoup_verify (&coin->denom_pub_hash,
251 : coin_bks,
252 : &coin->coin_pub,
253 : coin_sig))
254 : {
255 0 : GNUNET_break_op (0);
256 0 : return TALER_MHD_reply_with_error (
257 : connection,
258 : MHD_HTTP_FORBIDDEN,
259 : TALER_EC_EXCHANGE_RECOUP_SIGNATURE_INVALID,
260 : NULL);
261 : }
262 :
263 : /* re-compute client-side blinding so we can
264 : (a bit later) check that this coin was indeed
265 : signed by us. */
266 : {
267 : struct TALER_CoinPubHashP c_hash;
268 : struct TALER_BlindedPlanchet blinded_planchet;
269 :
270 0 : if (GNUNET_OK !=
271 0 : TALER_denom_blind (&dk->denom_pub,
272 : coin_bks,
273 : &coin->h_age_commitment,
274 : &coin->coin_pub,
275 : exchange_vals,
276 : &c_hash,
277 : &blinded_planchet))
278 : {
279 0 : GNUNET_break (0);
280 0 : return TALER_MHD_reply_with_error (
281 : connection,
282 : MHD_HTTP_INTERNAL_SERVER_ERROR,
283 : TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED,
284 : NULL);
285 : }
286 0 : if (TALER_DENOMINATION_CS == blinded_planchet.cipher)
287 : blinded_planchet.details.cs_blinded_planchet.nonce
288 0 : = *nonce;
289 0 : if (GNUNET_OK !=
290 0 : TALER_coin_ev_hash (&blinded_planchet,
291 : &coin->denom_pub_hash,
292 : &pc.h_coin_ev))
293 : {
294 0 : GNUNET_break (0);
295 0 : return TALER_MHD_reply_with_error (connection,
296 : MHD_HTTP_INTERNAL_SERVER_ERROR,
297 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
298 : NULL);
299 : }
300 0 : TALER_blinded_planchet_free (&blinded_planchet);
301 : }
302 :
303 0 : pc.coin_sig = coin_sig;
304 0 : pc.coin_bks = coin_bks;
305 0 : pc.coin = coin;
306 :
307 : {
308 0 : MHD_RESULT mhd_ret = MHD_NO;
309 : enum GNUNET_DB_QueryStatus qs;
310 :
311 : /* make sure coin is 'known' in database */
312 0 : qs = TEH_make_coin_known (coin,
313 : connection,
314 : &pc.known_coin_id,
315 : &mhd_ret);
316 : /* no transaction => no serialization failures should be possible */
317 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
318 0 : if (qs < 0)
319 0 : return mhd_ret;
320 : }
321 :
322 : {
323 : enum GNUNET_DB_QueryStatus qs;
324 :
325 0 : qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls,
326 : &pc.h_coin_ev,
327 : &pc.reserve_pub,
328 : &pc.reserve_out_serial_id);
329 0 : if (0 > qs)
330 : {
331 0 : GNUNET_break (0);
332 0 : return TALER_MHD_reply_with_error (
333 : connection,
334 : MHD_HTTP_INTERNAL_SERVER_ERROR,
335 : TALER_EC_GENERIC_DB_FETCH_FAILED,
336 : "get_reserve_by_h_blind");
337 : }
338 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
339 : {
340 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
341 : "Recoup requested for unknown envelope %s\n",
342 : GNUNET_h2s (&pc.h_coin_ev.hash));
343 0 : return TALER_MHD_reply_with_error (
344 : connection,
345 : MHD_HTTP_NOT_FOUND,
346 : TALER_EC_EXCHANGE_RECOUP_WITHDRAW_NOT_FOUND,
347 : NULL);
348 : }
349 : }
350 :
351 : /* Perform actual recoup transaction */
352 : {
353 : MHD_RESULT mhd_ret;
354 :
355 0 : if (GNUNET_OK !=
356 0 : TEH_DB_run_transaction (connection,
357 : "run recoup",
358 : TEH_MT_REQUEST_OTHER,
359 : &mhd_ret,
360 : &recoup_transaction,
361 : &pc))
362 0 : return mhd_ret;
363 : }
364 : /* Recoup succeeded, return result */
365 0 : return TALER_MHD_REPLY_JSON_PACK (connection,
366 : MHD_HTTP_OK,
367 : GNUNET_JSON_pack_data_auto (
368 : "reserve_pub",
369 : &pc.reserve_pub));
370 : }
371 :
372 :
373 : /**
374 : * Handle a "/coins/$COIN_PUB/recoup" request. Parses the JSON, and, if
375 : * successful, passes the JSON data to #verify_and_execute_recoup() to further
376 : * check the details of the operation specified. If everything checks out,
377 : * this will ultimately lead to the refund being executed, or rejected.
378 : *
379 : * @param connection the MHD connection to handle
380 : * @param coin_pub public key of the coin
381 : * @param root uploaded JSON data
382 : * @return MHD result code
383 : */
384 : MHD_RESULT
385 0 : TEH_handler_recoup (struct MHD_Connection *connection,
386 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
387 : const json_t *root)
388 : {
389 : enum GNUNET_GenericReturnValue ret;
390 : struct TALER_CoinPublicInfo coin;
391 : union TALER_DenominationBlindingKeyP coin_bks;
392 : struct TALER_CoinSpendSignatureP coin_sig;
393 : struct TALER_ExchangeWithdrawValues exchange_vals;
394 : struct TALER_CsNonce nonce;
395 : struct GNUNET_JSON_Specification spec[] = {
396 0 : GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
397 : &coin.denom_pub_hash),
398 0 : TALER_JSON_spec_denom_sig ("denom_sig",
399 : &coin.denom_sig),
400 0 : TALER_JSON_spec_exchange_withdraw_values ("ewv",
401 : &exchange_vals),
402 0 : GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret",
403 : &coin_bks),
404 0 : GNUNET_JSON_spec_fixed_auto ("coin_sig",
405 : &coin_sig),
406 0 : GNUNET_JSON_spec_mark_optional (
407 : GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
408 : &coin.h_age_commitment),
409 : &coin.no_age_commitment),
410 0 : GNUNET_JSON_spec_mark_optional (
411 : GNUNET_JSON_spec_fixed_auto ("cs_nonce",
412 : &nonce),
413 : NULL),
414 0 : GNUNET_JSON_spec_end ()
415 : };
416 :
417 0 : memset (&coin,
418 : 0,
419 : sizeof (coin));
420 0 : memset (&nonce,
421 : 0,
422 : sizeof (nonce));
423 0 : coin.coin_pub = *coin_pub;
424 0 : ret = TALER_MHD_parse_json_data (connection,
425 : root,
426 : spec);
427 0 : if (GNUNET_SYSERR == ret)
428 0 : return MHD_NO; /* hard failure */
429 0 : if (GNUNET_NO == ret)
430 0 : return MHD_YES; /* failure */
431 : {
432 : MHD_RESULT res;
433 :
434 0 : res = verify_and_execute_recoup (connection,
435 : &coin,
436 : &exchange_vals,
437 : &coin_bks,
438 : &nonce,
439 : &coin_sig);
440 0 : GNUNET_JSON_parse_free (spec);
441 0 : return res;
442 : }
443 : }
444 :
445 :
446 : /* end of taler-exchange-httpd_recoup.c */
|