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