Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-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_deposit.c
18 : * @brief Handle /deposit requests; parses the POST and JSON and
19 : * verifies the coin signature before handing things off
20 : * to the database.
21 : * @author Florian Dold
22 : * @author Benedikt Mueller
23 : * @author Christian Grothoff
24 : */
25 : #include "platform.h"
26 : #include <gnunet/gnunet_util_lib.h>
27 : #include <gnunet/gnunet_json_lib.h>
28 : #include <jansson.h>
29 : #include <microhttpd.h>
30 : #include <pthread.h>
31 : #include "taler_json_lib.h"
32 : #include "taler_mhd_lib.h"
33 : #include "taler-exchange-httpd_deposit.h"
34 : #include "taler-exchange-httpd_responses.h"
35 : #include "taler_exchangedb_lib.h"
36 : #include "taler-exchange-httpd_keys.h"
37 :
38 :
39 : /**
40 : * Send confirmation of deposit success to client. This function
41 : * will create a signed message affirming the given information
42 : * and return it to the client. By this, the exchange affirms that
43 : * the coin had sufficient (residual) value for the specified
44 : * transaction and that it will execute the requested deposit
45 : * operation with the given wiring details.
46 : *
47 : * @param connection connection to the client
48 : * @param coin_pub public key of the coin
49 : * @param h_wire hash of wire details
50 : * @param h_extensions hash of applicable extensions
51 : * @param h_contract_terms hash of contract details
52 : * @param exchange_timestamp exchange's timestamp
53 : * @param refund_deadline until when this deposit be refunded
54 : * @param wire_deadline until when will the exchange wire the funds
55 : * @param merchant merchant public key
56 : * @param amount_without_fee fraction of coin value to deposit, without the fee
57 : * @return MHD result code
58 : */
59 : static MHD_RESULT
60 0 : reply_deposit_success (
61 : struct MHD_Connection *connection,
62 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
63 : const struct TALER_MerchantWireHashP *h_wire,
64 : const struct TALER_ExtensionContractHashP *h_extensions,
65 : const struct TALER_PrivateContractHashP *h_contract_terms,
66 : struct GNUNET_TIME_Timestamp exchange_timestamp,
67 : struct GNUNET_TIME_Timestamp refund_deadline,
68 : struct GNUNET_TIME_Timestamp wire_deadline,
69 : const struct TALER_MerchantPublicKeyP *merchant,
70 : const struct TALER_Amount *amount_without_fee)
71 : {
72 : struct TALER_ExchangePublicKeyP pub;
73 : struct TALER_ExchangeSignatureP sig;
74 : enum TALER_ErrorCode ec;
75 :
76 0 : if (TALER_EC_NONE !=
77 0 : (ec = TALER_exchange_online_deposit_confirmation_sign (
78 : &TEH_keys_exchange_sign_,
79 : h_contract_terms,
80 : h_wire,
81 : h_extensions,
82 : exchange_timestamp,
83 : wire_deadline,
84 : refund_deadline,
85 : amount_without_fee,
86 : coin_pub,
87 : merchant,
88 : &pub,
89 : &sig)))
90 : {
91 0 : return TALER_MHD_reply_with_ec (connection,
92 : ec,
93 : NULL);
94 : }
95 0 : return TALER_MHD_REPLY_JSON_PACK (
96 : connection,
97 : MHD_HTTP_OK,
98 : GNUNET_JSON_pack_timestamp ("exchange_timestamp",
99 : exchange_timestamp),
100 : GNUNET_JSON_pack_data_auto ("exchange_sig",
101 : &sig),
102 : GNUNET_JSON_pack_data_auto ("exchange_pub",
103 : &pub));
104 : }
105 :
106 :
107 : /**
108 : * Closure for #deposit_transaction.
109 : */
110 : struct DepositContext
111 : {
112 : /**
113 : * Information about the deposit request.
114 : */
115 : const struct TALER_EXCHANGEDB_Deposit *deposit;
116 :
117 : /**
118 : * Our timestamp (when we received the request).
119 : * Possibly updated by the transaction if the
120 : * request is idempotent (was repeated).
121 : */
122 : struct GNUNET_TIME_Timestamp exchange_timestamp;
123 :
124 : /**
125 : * Hash of the payto URI.
126 : */
127 : struct TALER_PaytoHashP h_payto;
128 :
129 : /**
130 : * Row of of the coin in the known_coins table.
131 : */
132 : uint64_t known_coin_id;
133 :
134 : };
135 :
136 :
137 : /**
138 : * Execute database transaction for /deposit. Runs the transaction
139 : * logic; IF it returns a non-error code, the transaction logic MUST
140 : * NOT queue a MHD response. IF it returns an hard error, the
141 : * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
142 : * it returns the soft error code, the function MAY be called again to
143 : * retry and MUST not queue a MHD response.
144 : *
145 : * @param cls a `struct DepositContext`
146 : * @param connection MHD request context
147 : * @param[out] mhd_ret set to MHD status on error
148 : * @return transaction status
149 : */
150 : static enum GNUNET_DB_QueryStatus
151 0 : deposit_transaction (void *cls,
152 : struct MHD_Connection *connection,
153 : MHD_RESULT *mhd_ret)
154 : {
155 0 : struct DepositContext *dc = cls;
156 : enum GNUNET_DB_QueryStatus qs;
157 : bool balance_ok;
158 : bool in_conflict;
159 :
160 0 : qs = TEH_make_coin_known (&dc->deposit->coin,
161 : connection,
162 : &dc->known_coin_id,
163 : mhd_ret);
164 0 : if (qs < 0)
165 0 : return qs;
166 0 : qs = TEH_plugin->do_deposit (TEH_plugin->cls,
167 : dc->deposit,
168 : dc->known_coin_id,
169 0 : &dc->h_payto,
170 : false, /* FIXME-OEC: extension blocked #7270 */
171 : &dc->exchange_timestamp,
172 : &balance_ok,
173 : &in_conflict);
174 0 : if (qs < 0)
175 : {
176 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
177 0 : return qs;
178 0 : TALER_LOG_WARNING ("Failed to store /deposit information in database\n");
179 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
180 : MHD_HTTP_INTERNAL_SERVER_ERROR,
181 : TALER_EC_GENERIC_DB_STORE_FAILED,
182 : "deposit");
183 0 : return qs;
184 : }
185 0 : if (in_conflict)
186 : {
187 : /* FIXME #7267: conflicting contract != insufficient funds */
188 : *mhd_ret
189 0 : = TEH_RESPONSE_reply_coin_insufficient_funds (
190 : connection,
191 : TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
192 0 : &dc->deposit->coin.denom_pub_hash,
193 0 : &dc->deposit->coin.coin_pub);
194 0 : return GNUNET_DB_STATUS_HARD_ERROR;
195 : }
196 0 : if (! balance_ok)
197 : {
198 : *mhd_ret
199 0 : = TEH_RESPONSE_reply_coin_insufficient_funds (
200 : connection,
201 : TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
202 0 : &dc->deposit->coin.denom_pub_hash,
203 0 : &dc->deposit->coin.coin_pub);
204 0 : return GNUNET_DB_STATUS_HARD_ERROR;
205 : }
206 0 : TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT]++;
207 0 : return qs;
208 : }
209 :
210 :
211 : MHD_RESULT
212 0 : TEH_handler_deposit (struct MHD_Connection *connection,
213 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
214 : const json_t *root)
215 : {
216 : struct DepositContext dc;
217 : struct TALER_EXCHANGEDB_Deposit deposit;
218 : const char *payto_uri;
219 : struct GNUNET_JSON_Specification spec[] = {
220 0 : GNUNET_JSON_spec_string ("merchant_payto_uri",
221 : &payto_uri),
222 0 : GNUNET_JSON_spec_fixed_auto ("wire_salt",
223 : &deposit.wire_salt),
224 0 : TALER_JSON_spec_amount ("contribution",
225 : TEH_currency,
226 : &deposit.amount_with_fee),
227 0 : GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
228 : &deposit.coin.denom_pub_hash),
229 0 : TALER_JSON_spec_denom_sig ("ub_sig",
230 : &deposit.coin.denom_sig),
231 0 : GNUNET_JSON_spec_fixed_auto ("merchant_pub",
232 : &deposit.merchant_pub),
233 0 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
234 : &deposit.h_contract_terms),
235 0 : GNUNET_JSON_spec_mark_optional (
236 : GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
237 : &deposit.coin.h_age_commitment),
238 : &deposit.coin.no_age_commitment),
239 0 : GNUNET_JSON_spec_fixed_auto ("coin_sig",
240 : &deposit.csig),
241 0 : GNUNET_JSON_spec_timestamp ("timestamp",
242 : &deposit.timestamp),
243 0 : GNUNET_JSON_spec_mark_optional (
244 : GNUNET_JSON_spec_timestamp ("refund_deadline",
245 : &deposit.refund_deadline),
246 : NULL),
247 0 : GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
248 : &deposit.wire_deadline),
249 0 : GNUNET_JSON_spec_end ()
250 : };
251 : struct TALER_MerchantWireHashP h_wire;
252 :
253 0 : memset (&deposit,
254 : 0,
255 : sizeof (deposit));
256 0 : deposit.coin.coin_pub = *coin_pub;
257 : {
258 : enum GNUNET_GenericReturnValue res;
259 :
260 0 : res = TALER_MHD_parse_json_data (connection,
261 : root,
262 : spec);
263 0 : if (GNUNET_SYSERR == res)
264 : {
265 0 : GNUNET_break (0);
266 0 : return MHD_NO; /* hard failure */
267 : }
268 0 : if (GNUNET_NO == res)
269 : {
270 0 : GNUNET_break_op (0);
271 0 : return MHD_YES; /* failure */
272 : }
273 : }
274 : /* validate merchant's wire details (as far as we can) */
275 : {
276 : char *emsg;
277 :
278 0 : emsg = TALER_payto_validate (payto_uri);
279 0 : if (NULL != emsg)
280 : {
281 : MHD_RESULT ret;
282 :
283 0 : GNUNET_break_op (0);
284 0 : GNUNET_JSON_parse_free (spec);
285 0 : ret = TALER_MHD_reply_with_error (connection,
286 : MHD_HTTP_BAD_REQUEST,
287 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
288 : emsg);
289 0 : GNUNET_free (emsg);
290 0 : return ret;
291 : }
292 : }
293 0 : if (GNUNET_TIME_timestamp_cmp (deposit.refund_deadline,
294 : >,
295 : deposit.wire_deadline))
296 : {
297 0 : GNUNET_break_op (0);
298 0 : GNUNET_JSON_parse_free (spec);
299 0 : return TALER_MHD_reply_with_error (connection,
300 : MHD_HTTP_BAD_REQUEST,
301 : TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
302 : NULL);
303 : }
304 0 : if (GNUNET_TIME_absolute_is_never (deposit.wire_deadline.abs_time))
305 : {
306 0 : GNUNET_break_op (0);
307 0 : GNUNET_JSON_parse_free (spec);
308 0 : return TALER_MHD_reply_with_error (connection,
309 : MHD_HTTP_BAD_REQUEST,
310 : TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER,
311 : NULL);
312 : }
313 0 : deposit.receiver_wire_account = (char *) payto_uri;
314 0 : TALER_payto_hash (payto_uri,
315 : &dc.h_payto);
316 0 : TALER_merchant_wire_signature_hash (payto_uri,
317 : &deposit.wire_salt,
318 : &h_wire);
319 0 : dc.deposit = &deposit;
320 :
321 : /* new deposit */
322 0 : dc.exchange_timestamp = GNUNET_TIME_timestamp_get ();
323 : /* check denomination exists and is valid */
324 : {
325 : struct TEH_DenominationKey *dk;
326 : MHD_RESULT mret;
327 :
328 0 : dk = TEH_keys_denomination_by_hash (&deposit.coin.denom_pub_hash,
329 : connection,
330 : &mret);
331 0 : if (NULL == dk)
332 : {
333 0 : GNUNET_JSON_parse_free (spec);
334 0 : return mret;
335 : }
336 0 : if (0 > TALER_amount_cmp (&dk->meta.value,
337 : &deposit.amount_with_fee))
338 : {
339 0 : GNUNET_break_op (0);
340 0 : GNUNET_JSON_parse_free (spec);
341 0 : return TALER_MHD_reply_with_error (connection,
342 : MHD_HTTP_BAD_REQUEST,
343 : TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
344 : NULL);
345 : }
346 0 : if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
347 : {
348 : /* This denomination is past the expiration time for deposits */
349 0 : GNUNET_JSON_parse_free (spec);
350 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
351 : connection,
352 : &deposit.coin.denom_pub_hash,
353 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
354 : "DEPOSIT");
355 : }
356 0 : if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
357 : {
358 : /* This denomination is not yet valid */
359 0 : GNUNET_JSON_parse_free (spec);
360 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
361 : connection,
362 : &deposit.coin.denom_pub_hash,
363 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
364 : "DEPOSIT");
365 : }
366 0 : if (dk->recoup_possible)
367 : {
368 : /* This denomination has been revoked */
369 0 : GNUNET_JSON_parse_free (spec);
370 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
371 : connection,
372 : &deposit.coin.denom_pub_hash,
373 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
374 : "DEPOSIT");
375 : }
376 0 : if (dk->denom_pub.cipher != deposit.coin.denom_sig.cipher)
377 : {
378 : /* denomination cipher and denomination signature cipher not the same */
379 0 : GNUNET_JSON_parse_free (spec);
380 0 : return TALER_MHD_reply_with_error (connection,
381 : MHD_HTTP_BAD_REQUEST,
382 : TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
383 : NULL);
384 : }
385 :
386 0 : deposit.deposit_fee = dk->meta.fees.deposit;
387 : /* check coin signature */
388 0 : switch (dk->denom_pub.cipher)
389 : {
390 0 : case TALER_DENOMINATION_RSA:
391 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
392 0 : break;
393 0 : case TALER_DENOMINATION_CS:
394 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
395 0 : break;
396 0 : default:
397 0 : break;
398 : }
399 0 : if (GNUNET_YES !=
400 0 : TALER_test_coin_valid (&deposit.coin,
401 0 : &dk->denom_pub))
402 : {
403 0 : TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
404 0 : GNUNET_JSON_parse_free (spec);
405 0 : return TALER_MHD_reply_with_error (connection,
406 : MHD_HTTP_FORBIDDEN,
407 : TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
408 : NULL);
409 : }
410 : }
411 0 : if (0 < TALER_amount_cmp (&deposit.deposit_fee,
412 : &deposit.amount_with_fee))
413 : {
414 0 : GNUNET_break_op (0);
415 0 : GNUNET_JSON_parse_free (spec);
416 0 : return TALER_MHD_reply_with_error (connection,
417 : MHD_HTTP_BAD_REQUEST,
418 : TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
419 : NULL);
420 : }
421 :
422 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
423 0 : if (GNUNET_OK !=
424 0 : TALER_wallet_deposit_verify (&deposit.amount_with_fee,
425 : &deposit.deposit_fee,
426 : &h_wire,
427 : &deposit.h_contract_terms,
428 : &deposit.coin.h_age_commitment,
429 : NULL /* FIXME: h_extensions! */,
430 : &deposit.coin.denom_pub_hash,
431 : deposit.timestamp,
432 : &deposit.merchant_pub,
433 : deposit.refund_deadline,
434 : &deposit.coin.coin_pub,
435 : &deposit.csig))
436 : {
437 0 : TALER_LOG_WARNING ("Invalid signature on /deposit request\n");
438 0 : GNUNET_JSON_parse_free (spec);
439 0 : return TALER_MHD_reply_with_error (connection,
440 : MHD_HTTP_FORBIDDEN,
441 : TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
442 : NULL);
443 : }
444 :
445 0 : if (GNUNET_SYSERR ==
446 0 : TEH_plugin->preflight (TEH_plugin->cls))
447 : {
448 0 : GNUNET_break (0);
449 0 : return TALER_MHD_reply_with_error (connection,
450 : MHD_HTTP_INTERNAL_SERVER_ERROR,
451 : TALER_EC_GENERIC_DB_START_FAILED,
452 : "preflight failure");
453 : }
454 :
455 : /* execute transaction */
456 : {
457 : MHD_RESULT mhd_ret;
458 :
459 0 : if (GNUNET_OK !=
460 0 : TEH_DB_run_transaction (connection,
461 : "execute deposit",
462 : TEH_MT_REQUEST_DEPOSIT,
463 : &mhd_ret,
464 : &deposit_transaction,
465 : &dc))
466 : {
467 0 : GNUNET_JSON_parse_free (spec);
468 0 : return mhd_ret;
469 : }
470 : }
471 :
472 : /* generate regular response */
473 : {
474 : struct TALER_Amount amount_without_fee;
475 : MHD_RESULT res;
476 :
477 0 : GNUNET_assert (0 <=
478 : TALER_amount_subtract (&amount_without_fee,
479 : &deposit.amount_with_fee,
480 : &deposit.deposit_fee));
481 0 : res = reply_deposit_success (connection,
482 : &deposit.coin.coin_pub,
483 : &h_wire,
484 : NULL /* FIXME: h_extensions! */,
485 : &deposit.h_contract_terms,
486 : dc.exchange_timestamp,
487 : deposit.refund_deadline,
488 : deposit.wire_deadline,
489 : &deposit.merchant_pub,
490 : &amount_without_fee);
491 0 : GNUNET_JSON_parse_free (spec);
492 0 : return res;
493 : }
494 : }
495 :
496 :
497 : /* end of taler-exchange-httpd_deposit.c */
|