Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 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_common_deposit.c
18 : * @brief shared logic for handling deposited coins
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include "taler-exchange-httpd_common_deposit.h"
23 : #include "taler-exchange-httpd.h"
24 : #include "taler-exchange-httpd_keys.h"
25 :
26 :
27 : enum GNUNET_GenericReturnValue
28 0 : TEH_common_purse_deposit_parse_coin (
29 : struct MHD_Connection *connection,
30 : struct TEH_PurseDepositedCoin *coin,
31 : const json_t *jcoin)
32 : {
33 : struct GNUNET_JSON_Specification spec[] = {
34 0 : TALER_JSON_spec_amount ("amount",
35 : TEH_currency,
36 : &coin->amount),
37 0 : GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
38 : &coin->cpi.denom_pub_hash),
39 0 : TALER_JSON_spec_denom_sig ("ub_sig",
40 : &coin->cpi.denom_sig),
41 0 : GNUNET_JSON_spec_mark_optional (
42 0 : GNUNET_JSON_spec_fixed_auto ("attest",
43 : &coin->attest),
44 : &coin->no_attest),
45 0 : GNUNET_JSON_spec_mark_optional (
46 : TALER_JSON_spec_age_commitment ("age_commitment",
47 : &coin->age_commitment),
48 : &coin->cpi.no_age_commitment),
49 0 : GNUNET_JSON_spec_fixed_auto ("coin_sig",
50 : &coin->coin_sig),
51 0 : GNUNET_JSON_spec_fixed_auto ("coin_pub",
52 : &coin->cpi.coin_pub),
53 0 : GNUNET_JSON_spec_end ()
54 : };
55 :
56 0 : memset (coin,
57 : 0,
58 : sizeof (*coin));
59 0 : coin->cpi.no_age_commitment = true;
60 0 : coin->no_attest = true;
61 : {
62 : enum GNUNET_GenericReturnValue res;
63 :
64 0 : res = TALER_MHD_parse_json_data (connection,
65 : jcoin,
66 : spec);
67 0 : if (GNUNET_OK != res)
68 0 : return res;
69 : }
70 :
71 0 : if (! coin->cpi.no_age_commitment)
72 0 : TALER_age_commitment_hash (&coin->age_commitment,
73 : &coin->cpi.h_age_commitment);
74 :
75 : /* check denomination exists and is valid */
76 : {
77 : struct TEH_DenominationKey *dk;
78 : MHD_RESULT mret;
79 :
80 0 : dk = TEH_keys_denomination_by_hash (&coin->cpi.denom_pub_hash,
81 : connection,
82 : &mret);
83 0 : if (NULL == dk)
84 : {
85 0 : GNUNET_JSON_parse_free (spec);
86 0 : return (MHD_YES == mret) ? GNUNET_NO : GNUNET_SYSERR;
87 : }
88 0 : if (0 > TALER_amount_cmp (&dk->meta.value,
89 0 : &coin->amount))
90 : {
91 0 : GNUNET_break_op (0);
92 0 : GNUNET_JSON_parse_free (spec);
93 : return (MHD_YES ==
94 0 : TALER_MHD_reply_with_error (connection,
95 : MHD_HTTP_BAD_REQUEST,
96 : TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
97 : NULL))
98 0 : ? GNUNET_NO : GNUNET_SYSERR;
99 : }
100 0 : if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
101 : {
102 : /* This denomination is past the expiration time for deposits */
103 0 : GNUNET_JSON_parse_free (spec);
104 : return (MHD_YES ==
105 0 : TEH_RESPONSE_reply_expired_denom_pub_hash (
106 : connection,
107 0 : &coin->cpi.denom_pub_hash,
108 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
109 : "PURSE CREATE"))
110 0 : ? GNUNET_NO : GNUNET_SYSERR;
111 : }
112 0 : if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
113 : {
114 : /* This denomination is not yet valid */
115 0 : GNUNET_JSON_parse_free (spec);
116 : return (MHD_YES ==
117 0 : TEH_RESPONSE_reply_expired_denom_pub_hash (
118 : connection,
119 0 : &coin->cpi.denom_pub_hash,
120 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
121 : "PURSE CREATE"))
122 0 : ? GNUNET_NO : GNUNET_SYSERR;
123 : }
124 0 : if (dk->recoup_possible)
125 : {
126 : /* This denomination has been revoked */
127 0 : GNUNET_JSON_parse_free (spec);
128 : return (MHD_YES ==
129 0 : TEH_RESPONSE_reply_expired_denom_pub_hash (
130 : connection,
131 0 : &coin->cpi.denom_pub_hash,
132 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
133 : "PURSE CREATE"))
134 0 : ? GNUNET_NO : GNUNET_SYSERR;
135 : }
136 0 : if (dk->denom_pub.cipher != coin->cpi.denom_sig.cipher)
137 : {
138 : /* denomination cipher and denomination signature cipher not the same */
139 0 : GNUNET_JSON_parse_free (spec);
140 : return (MHD_YES ==
141 0 : TALER_MHD_reply_with_error (connection,
142 : MHD_HTTP_BAD_REQUEST,
143 : TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
144 : NULL))
145 0 : ? GNUNET_NO : GNUNET_SYSERR;
146 : }
147 :
148 0 : coin->deposit_fee = dk->meta.fees.deposit;
149 0 : if (0 < TALER_amount_cmp (&coin->deposit_fee,
150 0 : &coin->amount))
151 : {
152 0 : GNUNET_break_op (0);
153 0 : GNUNET_JSON_parse_free (spec);
154 0 : return TALER_MHD_reply_with_error (connection,
155 : MHD_HTTP_BAD_REQUEST,
156 : TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
157 : NULL);
158 : }
159 0 : GNUNET_assert (0 <=
160 : TALER_amount_subtract (&coin->amount_minus_fee,
161 : &coin->amount,
162 : &coin->deposit_fee));
163 :
164 : /* check coin signature */
165 0 : switch (dk->denom_pub.cipher)
166 : {
167 0 : case TALER_DENOMINATION_RSA:
168 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
169 0 : break;
170 0 : case TALER_DENOMINATION_CS:
171 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
172 0 : break;
173 0 : default:
174 0 : break;
175 : }
176 0 : if (GNUNET_YES !=
177 0 : TALER_test_coin_valid (&coin->cpi,
178 0 : &dk->denom_pub))
179 : {
180 0 : TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
181 0 : GNUNET_JSON_parse_free (spec);
182 : return (MHD_YES ==
183 0 : TALER_MHD_reply_with_error (connection,
184 : MHD_HTTP_FORBIDDEN,
185 : TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
186 : NULL))
187 0 : ? GNUNET_NO : GNUNET_SYSERR;
188 : }
189 : }
190 0 : return GNUNET_OK;
191 : }
192 :
193 :
194 : enum GNUNET_GenericReturnValue
195 0 : TEH_common_deposit_check_purse_deposit (
196 : struct MHD_Connection *connection,
197 : const struct TEH_PurseDepositedCoin *coin,
198 : const struct TALER_PurseContractPublicKeyP *purse_pub,
199 : uint32_t min_age)
200 : {
201 0 : if (GNUNET_OK !=
202 0 : TALER_wallet_purse_deposit_verify (TEH_base_url,
203 : purse_pub,
204 : &coin->amount,
205 : &coin->cpi.denom_pub_hash,
206 : &coin->cpi.h_age_commitment,
207 : &coin->cpi.coin_pub,
208 : &coin->coin_sig))
209 : {
210 0 : TALER_LOG_WARNING (
211 : "Invalid coin signature to deposit into purse\n");
212 : return (MHD_YES ==
213 0 : TALER_MHD_reply_with_error (connection,
214 : MHD_HTTP_FORBIDDEN,
215 : TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_SIGNATURE_INVALID,
216 : TEH_base_url))
217 : ? GNUNET_NO
218 0 : : GNUNET_SYSERR;
219 : }
220 :
221 : /* Check and verify the age restriction. */
222 0 : if (coin->no_attest != coin->cpi.no_age_commitment)
223 : {
224 0 : GNUNET_break_op (0);
225 0 : return TALER_MHD_reply_with_error (connection,
226 : MHD_HTTP_BAD_REQUEST,
227 : TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_CONFLICTING_ATTEST_VS_AGE_COMMITMENT,
228 : "mismatch of attest and age_commitment");
229 : }
230 :
231 0 : if (coin->cpi.no_age_commitment)
232 0 : return GNUNET_OK; /* unrestricted coin */
233 :
234 : /* age attestation must be valid */
235 0 : if (GNUNET_OK !=
236 0 : TALER_age_commitment_verify (&coin->age_commitment,
237 : min_age,
238 : &coin->attest))
239 : {
240 0 : GNUNET_break_op (0);
241 0 : return TALER_MHD_reply_with_error (connection,
242 : MHD_HTTP_BAD_REQUEST,
243 : TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_AGE_ATTESTATION_FAILURE,
244 : "invalid attest for minimum age");
245 : }
246 0 : return GNUNET_OK;
247 : }
248 :
249 :
250 : /**
251 : * Release data structures of @a coin. Note that
252 : * @a coin itself is NOT freed.
253 : *
254 : * @param[in] coin information to release
255 : */
256 : void
257 0 : TEH_common_purse_deposit_free_coin (struct TEH_PurseDepositedCoin *coin)
258 : {
259 0 : TALER_denom_sig_free (&coin->cpi.denom_sig);
260 0 : if (! coin->cpi.no_age_commitment)
261 0 : GNUNET_free (coin->age_commitment.keys); /* Only the keys have been allocated */
262 0 : }
263 :
264 :
265 : #if LEGACY
266 :
267 : if (0 >
268 : TALER_amount_add (&pcc->deposit_total,
269 : &pcc->deposit_total,
270 : &coin->amount_minus_fee))
271 : {
272 : GNUNET_break (0);
273 : return TALER_MHD_reply_with_error (connection,
274 : MHD_HTTP_INTERNAL_SERVER_ERROR,
275 : TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
276 : "total deposit contribution");
277 : }
278 :
279 :
280 : {
281 : MHD_RESULT mhd_ret = MHD_NO;
282 : enum GNUNET_DB_QueryStatus qs;
283 :
284 : /* make sure coin is 'known' in database */
285 : for (unsigned int tries = 0; tries<MAX_TRANSACTION_COMMIT_RETRIES; tries++)
286 : {
287 : qs = TEH_make_coin_known (&coin->cpi,
288 : connection,
289 : &coin->known_coin_id,
290 : &mhd_ret);
291 : /* no transaction => no serialization failures should be possible */
292 : if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
293 : break;
294 : }
295 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
296 : {
297 : GNUNET_break (0);
298 : return (MHD_YES ==
299 : TALER_MHD_reply_with_error (connection,
300 : MHD_HTTP_INTERNAL_SERVER_ERROR,
301 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
302 : "make_coin_known"))
303 : ? GNUNET_NO : GNUNET_SYSERR;
304 : }
305 : if (qs < 0)
306 : return (MHD_YES == mhd_ret) ? GNUNET_NO : GNUNET_SYSERR;
307 : }
308 : return GNUNET_OK;
309 : }
310 : #endif
|