diff options
Diffstat (limited to 'src/mint/taler-mint-httpd_withdraw.c')
-rw-r--r-- | src/mint/taler-mint-httpd_withdraw.c | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/src/mint/taler-mint-httpd_withdraw.c b/src/mint/taler-mint-httpd_withdraw.c new file mode 100644 index 000000000..7ffa45706 --- /dev/null +++ b/src/mint/taler-mint-httpd_withdraw.c | |||
@@ -0,0 +1,400 @@ | |||
1 | /* | ||
2 | This file is part of TALER | ||
3 | (C) 2014 GNUnet e.V. | ||
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, If not, see <http://www.gnu.org/licenses/> | ||
15 | */ | ||
16 | /** | ||
17 | * @file taler-mint-httpd_withdraw.c | ||
18 | * @brief Handle /withdraw/ requests | ||
19 | * @author Florian Dold | ||
20 | * @author Benedikt Mueller | ||
21 | * @author Christian Grothoff | ||
22 | */ | ||
23 | #include "platform.h" | ||
24 | #include <gnunet/gnunet_util_lib.h> | ||
25 | #include <jansson.h> | ||
26 | #include <microhttpd.h> | ||
27 | #include <libpq-fe.h> | ||
28 | #include <pthread.h> | ||
29 | #include "mint.h" | ||
30 | #include "mint_db.h" | ||
31 | #include "taler_types.h" | ||
32 | #include "taler_signatures.h" | ||
33 | #include "taler_rsa.h" | ||
34 | #include "taler_json_lib.h" | ||
35 | #include "taler_microhttpd_lib.h" | ||
36 | #include "taler-mint-httpd_keys.h" | ||
37 | #include "taler-mint-httpd_mhd.h" | ||
38 | #include "taler-mint-httpd_withdraw.h" | ||
39 | |||
40 | |||
41 | /** | ||
42 | * Convert a signature (with purpose) to | ||
43 | * a JSON object representation. | ||
44 | * | ||
45 | * @param purpose purpose of the signature | ||
46 | * @param signature the signature | ||
47 | * @return the JSON reporesentation of the signature with purpose | ||
48 | */ | ||
49 | static json_t * | ||
50 | sig_to_json (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose, | ||
51 | const struct GNUNET_CRYPTO_EddsaSignature *signature) | ||
52 | { | ||
53 | json_t *root; | ||
54 | json_t *el; | ||
55 | |||
56 | root = json_object (); | ||
57 | |||
58 | el = json_integer ((json_int_t) ntohl (purpose->size)); | ||
59 | json_object_set_new (root, "size", el); | ||
60 | |||
61 | el = json_integer ((json_int_t) ntohl (purpose->purpose)); | ||
62 | json_object_set_new (root, "purpose", el); | ||
63 | |||
64 | el = TALER_JSON_from_data (signature, sizeof (struct GNUNET_CRYPTO_EddsaSignature)); | ||
65 | json_object_set_new (root, "sig", el); | ||
66 | |||
67 | return root; | ||
68 | } | ||
69 | |||
70 | |||
71 | /** | ||
72 | * Sign a reserve's status with the current signing key. | ||
73 | * | ||
74 | * @param reserve the reserve to sign | ||
75 | * @param key_state the key state containing the current | ||
76 | * signing private key | ||
77 | */ | ||
78 | static void | ||
79 | sign_reserve (struct Reserve *reserve, | ||
80 | struct MintKeyState *key_state) | ||
81 | { | ||
82 | reserve->status_sign_pub = key_state->current_sign_key_issue.signkey_pub; | ||
83 | reserve->status_sig_purpose.purpose = htonl (TALER_SIGNATURE_RESERVE_STATUS); | ||
84 | reserve->status_sig_purpose.size = htonl (sizeof (struct Reserve) - | ||
85 | offsetof (struct Reserve, status_sig_purpose)); | ||
86 | GNUNET_CRYPTO_eddsa_sign (&key_state->current_sign_key_issue.signkey_priv, | ||
87 | &reserve->status_sig_purpose, | ||
88 | &reserve->status_sig); | ||
89 | } | ||
90 | |||
91 | |||
92 | /** | ||
93 | * Handle a "/withdraw/status" request | ||
94 | * | ||
95 | * @param rh context of the handler | ||
96 | * @param connection the MHD connection to handle | ||
97 | * @param[IN|OUT] connection_cls the connection's closure (can be updated) | ||
98 | * @param upload_data upload data | ||
99 | * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data | ||
100 | * @return MHD result code | ||
101 | */ | ||
102 | int | ||
103 | TALER_MINT_handler_withdraw_status (struct RequestHandler *rh, | ||
104 | struct MHD_Connection *connection, | ||
105 | void **connection_cls, | ||
106 | const char *upload_data, | ||
107 | size_t *upload_data_size) | ||
108 | { | ||
109 | struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; | ||
110 | PGconn *db_conn; | ||
111 | int res; | ||
112 | struct Reserve reserve; | ||
113 | struct MintKeyState *key_state; | ||
114 | int must_update = GNUNET_NO; | ||
115 | json_t *json; | ||
116 | |||
117 | res = TALER_MINT_mhd_request_arg_data (connection, | ||
118 | "reserve_pub", | ||
119 | &reserve_pub, | ||
120 | sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); | ||
121 | if (GNUNET_SYSERR == res) | ||
122 | { | ||
123 | // FIXME: return 'internal error' | ||
124 | GNUNET_break (0); | ||
125 | return MHD_NO; | ||
126 | } | ||
127 | if (GNUNET_OK != res) | ||
128 | return MHD_YES; | ||
129 | if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) | ||
130 | { | ||
131 | // FIXME: return 'internal error'? | ||
132 | GNUNET_break (0); | ||
133 | return MHD_NO; | ||
134 | } | ||
135 | res = TALER_MINT_DB_get_reserve (db_conn, | ||
136 | &reserve_pub, | ||
137 | &reserve); | ||
138 | if (GNUNET_SYSERR == res) | ||
139 | return TALER_MINT_helper_send_json_pack (rh, | ||
140 | connection, | ||
141 | connection_cls, | ||
142 | 0 /* no caching */, | ||
143 | MHD_HTTP_NOT_FOUND, | ||
144 | "{s:s}", | ||
145 | "error", | ||
146 | "Reserve not found"); | ||
147 | if (GNUNET_OK != res) | ||
148 | { | ||
149 | // FIXME: return 'internal error'? | ||
150 | GNUNET_break (0); | ||
151 | return MHD_NO; | ||
152 | } | ||
153 | key_state = TALER_MINT_key_state_acquire (); | ||
154 | if (0 != memcmp (&key_state->current_sign_key_issue.signkey_pub, | ||
155 | &reserve.status_sign_pub, | ||
156 | sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) | ||
157 | { | ||
158 | sign_reserve (&reserve, key_state); | ||
159 | must_update = GNUNET_YES; | ||
160 | } | ||
161 | if ((GNUNET_YES == must_update) && | ||
162 | (GNUNET_OK != TALER_MINT_DB_update_reserve (db_conn, &reserve, !must_update))) | ||
163 | { | ||
164 | GNUNET_break (0); | ||
165 | return MHD_YES; | ||
166 | } | ||
167 | |||
168 | /* Convert the public information of a reserve (i.e. | ||
169 | excluding private key) to a JSON object. */ | ||
170 | json = json_object (); | ||
171 | json_object_set_new (json, | ||
172 | "balance", | ||
173 | TALER_JSON_from_amount (TALER_amount_ntoh (reserve.balance))); | ||
174 | json_object_set_new (json, | ||
175 | "expiration", | ||
176 | TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (reserve.expiration))); | ||
177 | json_object_set_new (json, | ||
178 | "signature", | ||
179 | sig_to_json (&reserve.status_sig_purpose, | ||
180 | &reserve.status_sig)); | ||
181 | |||
182 | return send_response_json (connection, | ||
183 | json, | ||
184 | MHD_HTTP_OK); | ||
185 | } | ||
186 | |||
187 | |||
188 | /** | ||
189 | * Send positive, normal response for "/withdraw/sign". | ||
190 | * | ||
191 | * @param connection the connection to send the response to | ||
192 | * @param collectable the collectable blindcoin (i.e. the blindly signed coin) | ||
193 | * @return a MHD result code | ||
194 | */ | ||
195 | static int | ||
196 | helper_withdraw_sign_send_reply (struct MHD_Connection *connection, | ||
197 | const struct CollectableBlindcoin *collectable) | ||
198 | { | ||
199 | json_t *root = json_object (); | ||
200 | |||
201 | json_object_set_new (root, "ev_sig", | ||
202 | TALER_JSON_from_data (&collectable->ev_sig, | ||
203 | sizeof (struct TALER_RSA_Signature))); | ||
204 | return send_response_json (connection, | ||
205 | root, | ||
206 | MHD_HTTP_OK); | ||
207 | } | ||
208 | |||
209 | |||
210 | /** | ||
211 | * Handle a "/withdraw/sign" request | ||
212 | * | ||
213 | * @param rh context of the handler | ||
214 | * @param connection the MHD connection to handle | ||
215 | * @param[IN|OUT] connection_cls the connection's closure (can be updated) | ||
216 | * @param upload_data upload data | ||
217 | * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data | ||
218 | * @return MHD result code | ||
219 | */ | ||
220 | int | ||
221 | TALER_MINT_handler_withdraw_sign (struct RequestHandler *rh, | ||
222 | struct MHD_Connection *connection, | ||
223 | void **connection_cls, | ||
224 | const char *upload_data, | ||
225 | size_t *upload_data_size) | ||
226 | { | ||
227 | struct TALER_WithdrawRequest wsrd; | ||
228 | int res; | ||
229 | PGconn *db_conn; | ||
230 | struct Reserve reserve; | ||
231 | struct MintKeyState *key_state; | ||
232 | struct CollectableBlindcoin collectable; | ||
233 | struct TALER_MINT_DenomKeyIssue *dki; | ||
234 | struct TALER_RSA_Signature ev_sig; | ||
235 | struct TALER_Amount amount_required; | ||
236 | |||
237 | memset (&wsrd, | ||
238 | 0, | ||
239 | sizeof (struct TALER_WithdrawRequest)); | ||
240 | res = TALER_MINT_mhd_request_arg_data (connection, | ||
241 | "reserve_pub", | ||
242 | &wsrd.reserve_pub, | ||
243 | sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); | ||
244 | if (GNUNET_SYSERR == res) | ||
245 | { | ||
246 | // FIXME: return 'internal error'? | ||
247 | GNUNET_break (0); | ||
248 | return MHD_NO; | ||
249 | } | ||
250 | if (GNUNET_OK != res) | ||
251 | return MHD_YES; | ||
252 | res = TALER_MINT_mhd_request_arg_data (connection, | ||
253 | "denom_pub", | ||
254 | &wsrd.denomination_pub, | ||
255 | sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)); | ||
256 | if (GNUNET_SYSERR == res) | ||
257 | { | ||
258 | // FIXME: return 'internal error'? | ||
259 | GNUNET_break (0); | ||
260 | return MHD_NO; | ||
261 | } | ||
262 | if (GNUNET_OK != res) | ||
263 | return MHD_YES; | ||
264 | res = TALER_MINT_mhd_request_arg_data (connection, | ||
265 | "coin_ev", | ||
266 | &wsrd.coin_envelope, | ||
267 | sizeof (struct TALER_RSA_Signature)); | ||
268 | if (GNUNET_SYSERR == res) | ||
269 | { | ||
270 | // FIXME: return 'internal error'? | ||
271 | GNUNET_break (0); | ||
272 | return MHD_NO; | ||
273 | } | ||
274 | if (GNUNET_OK != res) | ||
275 | return MHD_YES; | ||
276 | res = TALER_MINT_mhd_request_arg_data (connection, | ||
277 | "reserve_sig", | ||
278 | &wsrd.sig, | ||
279 | sizeof (struct GNUNET_CRYPTO_EddsaSignature)); | ||
280 | if (GNUNET_SYSERR == res) | ||
281 | { | ||
282 | // FIXME: return 'internal error'? | ||
283 | GNUNET_break (0); | ||
284 | return MHD_NO; | ||
285 | } | ||
286 | if (GNUNET_OK != res) | ||
287 | return MHD_YES; | ||
288 | |||
289 | if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) | ||
290 | { | ||
291 | // FIXME: return 'internal error'? | ||
292 | GNUNET_break (0); | ||
293 | return MHD_NO; | ||
294 | } | ||
295 | |||
296 | res = TALER_MINT_DB_get_collectable_blindcoin (db_conn, | ||
297 | &wsrd.coin_envelope, | ||
298 | &collectable); | ||
299 | if (GNUNET_SYSERR == res) | ||
300 | { | ||
301 | // FIXME: return 'internal error' | ||
302 | GNUNET_break (0); | ||
303 | return MHD_NO; | ||
304 | } | ||
305 | |||
306 | /* Don't sign again if we have already signed the coin */ | ||
307 | if (GNUNET_YES == res) | ||
308 | return helper_withdraw_sign_send_reply (connection, | ||
309 | &collectable); | ||
310 | GNUNET_assert (GNUNET_NO == res); | ||
311 | res = TALER_MINT_DB_get_reserve (db_conn, | ||
312 | &wsrd.reserve_pub, | ||
313 | &reserve); | ||
314 | if (GNUNET_SYSERR == res) | ||
315 | { | ||
316 | // FIXME: return 'internal error' | ||
317 | GNUNET_break (0); | ||
318 | return MHD_NO; | ||
319 | } | ||
320 | if (GNUNET_NO == res) | ||
321 | return request_send_json_pack (connection, | ||
322 | MHD_HTTP_NOT_FOUND, | ||
323 | "{s:s}", | ||
324 | "error", "Reserve not found"); | ||
325 | |||
326 | // fill out all the missing info in the request before | ||
327 | // we can check the signature on the request | ||
328 | |||
329 | wsrd.purpose.purpose = htonl (TALER_SIGNATURE_WITHDRAW); | ||
330 | wsrd.purpose.size = htonl (sizeof (struct TALER_WithdrawRequest) - | ||
331 | offsetof (struct TALER_WithdrawRequest, purpose)); | ||
332 | |||
333 | if (GNUNET_OK != | ||
334 | GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WITHDRAW, | ||
335 | &wsrd.purpose, | ||
336 | &wsrd.sig, | ||
337 | &wsrd.reserve_pub)) | ||
338 | return request_send_json_pack (connection, | ||
339 | MHD_HTTP_UNAUTHORIZED, | ||
340 | "{s:s}", | ||
341 | "error", "Invalid Signature"); | ||
342 | |||
343 | key_state = TALER_MINT_key_state_acquire (); | ||
344 | dki = TALER_MINT_get_denom_key (key_state, | ||
345 | &wsrd.denomination_pub); | ||
346 | TALER_MINT_key_state_release (key_state); | ||
347 | if (NULL == dki) | ||
348 | return request_send_json_pack (connection, MHD_HTTP_NOT_FOUND, | ||
349 | "{s:s}", | ||
350 | "error", "Denomination not found"); | ||
351 | |||
352 | amount_required = TALER_amount_ntoh (dki->value); | ||
353 | amount_required = TALER_amount_add (amount_required, | ||
354 | TALER_amount_ntoh (dki->fee_withdraw)); | ||
355 | |||
356 | if (0 < TALER_amount_cmp (amount_required, | ||
357 | TALER_amount_ntoh (reserve.balance))) | ||
358 | return request_send_json_pack (connection, | ||
359 | MHD_HTTP_PAYMENT_REQUIRED, | ||
360 | "{s:s}", | ||
361 | "error", "Insufficient funds"); | ||
362 | if (GNUNET_OK != TALER_RSA_sign (dki->denom_priv, | ||
363 | &wsrd.coin_envelope, | ||
364 | sizeof (struct TALER_RSA_BlindedSignaturePurpose), | ||
365 | &ev_sig)) | ||
366 | { | ||
367 | // FIXME: return 'internal error' | ||
368 | GNUNET_break (0); | ||
369 | return MHD_NO; | ||
370 | } | ||
371 | |||
372 | reserve.balance = TALER_amount_hton (TALER_amount_subtract (TALER_amount_ntoh (reserve.balance), | ||
373 | amount_required)); | ||
374 | if (GNUNET_OK != | ||
375 | TALER_MINT_DB_update_reserve (db_conn, | ||
376 | &reserve, | ||
377 | GNUNET_YES)) | ||
378 | { | ||
379 | // FIXME: return 'internal error' | ||
380 | GNUNET_break (0); | ||
381 | return MHD_NO; | ||
382 | } | ||
383 | |||
384 | collectable.ev = wsrd.coin_envelope; | ||
385 | collectable.ev_sig = ev_sig; | ||
386 | collectable.reserve_pub = wsrd.reserve_pub; | ||
387 | collectable.reserve_sig = wsrd.sig; | ||
388 | if (GNUNET_OK != | ||
389 | TALER_MINT_DB_insert_collectable_blindcoin (db_conn, | ||
390 | &collectable)) | ||
391 | { | ||
392 | // FIXME: return 'internal error' | ||
393 | GNUNET_break (0); | ||
394 | return GNUNET_NO;; | ||
395 | } | ||
396 | return helper_withdraw_sign_send_reply (connection, | ||
397 | &collectable); | ||
398 | } | ||
399 | |||
400 | /* end of taler-mint-httpd_withdraw.c */ | ||