Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU 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 General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file lib/exchange_api_post-reserves-RESERVE_PUB-attest.c
19 : * @brief Implementation of the POST /reserves/$RESERVE_PUB/attest requests
20 : * @author Christian Grothoff
21 : */
22 : #include <jansson.h>
23 : #include <microhttpd.h> /* just for HTTP attest codes */
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <gnunet/gnunet_json_lib.h>
26 : #include <gnunet/gnunet_curl_lib.h>
27 : #include "taler/taler_json_lib.h"
28 : #include "exchange_api_handle.h"
29 : #include "taler/taler_signatures.h"
30 : #include "exchange_api_curl_defaults.h"
31 :
32 :
33 : /**
34 : * @brief A POST /reserves/$RID/attest Handle
35 : */
36 : struct TALER_EXCHANGE_PostReservesAttestHandle
37 : {
38 :
39 : /**
40 : * Reference to the execution context.
41 : */
42 : struct GNUNET_CURL_Context *ctx;
43 :
44 : /**
45 : * Base URL of the exchange.
46 : */
47 : char *base_url;
48 :
49 : /**
50 : * The url for this request, set during _start.
51 : */
52 : char *url;
53 :
54 : /**
55 : * Context for #TEH_curl_easy_post(). Keeps the data that must
56 : * persist for Curl to make the upload.
57 : */
58 : struct TALER_CURL_PostContext post_ctx;
59 :
60 : /**
61 : * Handle for the request.
62 : */
63 : struct GNUNET_CURL_Job *job;
64 :
65 : /**
66 : * Function to call with the result.
67 : */
68 : TALER_EXCHANGE_PostReservesAttestCallback cb;
69 :
70 : /**
71 : * Closure for @a cb.
72 : */
73 : TALER_EXCHANGE_POST_RESERVES_ATTEST_RESULT_CLOSURE *cb_cls;
74 :
75 : /**
76 : * The keys of the exchange this request handle will use.
77 : */
78 : struct TALER_EXCHANGE_Keys *keys;
79 :
80 : /**
81 : * Public key of the reserve we are querying.
82 : */
83 : struct TALER_ReservePublicKeyP reserve_pub;
84 :
85 : /**
86 : * Pre-built JSON body for the request.
87 : */
88 : json_t *body;
89 :
90 : };
91 :
92 :
93 : /**
94 : * We received an #MHD_HTTP_OK attest code. Handle the JSON
95 : * response.
96 : *
97 : * @param prah handle of the request
98 : * @param j JSON response
99 : * @return #GNUNET_OK on success
100 : */
101 : static enum GNUNET_GenericReturnValue
102 1 : handle_reserves_attest_ok (struct TALER_EXCHANGE_PostReservesAttestHandle *prah,
103 : const json_t *j)
104 : {
105 1 : struct TALER_EXCHANGE_PostReservesAttestResponse rs = {
106 : .hr.reply = j,
107 : .hr.http_status = MHD_HTTP_OK
108 : };
109 : const json_t *attributes;
110 : struct GNUNET_JSON_Specification spec[] = {
111 1 : GNUNET_JSON_spec_timestamp ("exchange_timestamp",
112 : &rs.details.ok.exchange_time),
113 1 : GNUNET_JSON_spec_timestamp ("expiration_time",
114 : &rs.details.ok.expiration_time),
115 1 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
116 : &rs.details.ok.exchange_sig),
117 1 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
118 : &rs.details.ok.exchange_pub),
119 1 : GNUNET_JSON_spec_object_const ("attributes",
120 : &attributes),
121 1 : GNUNET_JSON_spec_end ()
122 : };
123 :
124 1 : if (GNUNET_OK !=
125 1 : GNUNET_JSON_parse (j,
126 : spec,
127 : NULL,
128 : NULL))
129 : {
130 0 : GNUNET_break_op (0);
131 0 : return GNUNET_SYSERR;
132 : }
133 1 : if (GNUNET_OK !=
134 1 : TALER_EXCHANGE_test_signing_key (prah->keys,
135 : &rs.details.ok.exchange_pub))
136 : {
137 0 : GNUNET_break_op (0);
138 0 : rs.hr.http_status = 0;
139 0 : rs.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
140 0 : prah->cb (prah->cb_cls,
141 : &rs);
142 0 : prah->cb = NULL;
143 0 : GNUNET_JSON_parse_free (spec);
144 0 : return GNUNET_SYSERR;
145 : }
146 1 : rs.details.ok.attributes = attributes;
147 1 : if (GNUNET_OK !=
148 1 : TALER_exchange_online_reserve_attest_details_verify (
149 : rs.details.ok.exchange_time,
150 : rs.details.ok.expiration_time,
151 1 : &prah->reserve_pub,
152 : attributes,
153 : &rs.details.ok.exchange_pub,
154 : &rs.details.ok.exchange_sig))
155 : {
156 0 : GNUNET_break_op (0);
157 0 : GNUNET_JSON_parse_free (spec);
158 0 : return GNUNET_SYSERR;
159 : }
160 1 : prah->cb (prah->cb_cls,
161 : &rs);
162 1 : prah->cb = NULL;
163 1 : GNUNET_JSON_parse_free (spec);
164 1 : return GNUNET_OK;
165 : }
166 :
167 :
168 : /**
169 : * Function called when we're done processing the
170 : * HTTP /reserves/$RID/attest request.
171 : *
172 : * @param cls the `struct TALER_EXCHANGE_PostReservesAttestHandle`
173 : * @param response_code HTTP response code, 0 on error
174 : * @param response parsed JSON result, NULL on error
175 : */
176 : static void
177 1 : handle_reserves_attest_finished (void *cls,
178 : long response_code,
179 : const void *response)
180 : {
181 1 : struct TALER_EXCHANGE_PostReservesAttestHandle *prah = cls;
182 1 : const json_t *j = response;
183 1 : struct TALER_EXCHANGE_PostReservesAttestResponse rs = {
184 : .hr.reply = j,
185 1 : .hr.http_status = (unsigned int) response_code
186 : };
187 :
188 1 : prah->job = NULL;
189 1 : switch (response_code)
190 : {
191 0 : case 0:
192 0 : rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
193 0 : break;
194 1 : case MHD_HTTP_OK:
195 1 : if (GNUNET_OK !=
196 1 : handle_reserves_attest_ok (prah,
197 : j))
198 : {
199 0 : rs.hr.http_status = 0;
200 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
201 : }
202 1 : break;
203 0 : case MHD_HTTP_BAD_REQUEST:
204 : /* This should never happen, either us or the exchange is buggy
205 : (or API version conflict); just pass JSON reply to the application */
206 0 : GNUNET_break (0);
207 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
208 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
209 0 : break;
210 0 : case MHD_HTTP_FORBIDDEN:
211 : /* This should never happen, either us or the exchange is buggy
212 : (or API version conflict); just pass JSON reply to the application */
213 0 : GNUNET_break (0);
214 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
215 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
216 0 : break;
217 0 : case MHD_HTTP_NOT_FOUND:
218 : /* Nothing really to verify, this should never
219 : happen, we should pass the JSON reply to the application */
220 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
221 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
222 0 : break;
223 0 : case MHD_HTTP_CONFLICT:
224 : /* Server doesn't have the requested attributes */
225 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
226 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
227 0 : break;
228 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
229 : /* Server had an internal issue; we should retry, but this API
230 : leaves this to the application */
231 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
232 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
233 0 : break;
234 0 : default:
235 : /* unexpected response code */
236 0 : GNUNET_break_op (0);
237 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
238 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
239 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
240 : "Unexpected response code %u/%d for reserves attest\n",
241 : (unsigned int) response_code,
242 : (int) rs.hr.ec);
243 0 : break;
244 : }
245 1 : if (NULL != prah->cb)
246 : {
247 0 : prah->cb (prah->cb_cls,
248 : &rs);
249 0 : prah->cb = NULL;
250 : }
251 1 : TALER_EXCHANGE_post_reserves_attest_cancel (prah);
252 1 : }
253 :
254 :
255 : struct TALER_EXCHANGE_PostReservesAttestHandle *
256 1 : TALER_EXCHANGE_post_reserves_attest_create (
257 : struct GNUNET_CURL_Context *ctx,
258 : const char *url,
259 : struct TALER_EXCHANGE_Keys *keys,
260 : const struct TALER_ReservePrivateKeyP *reserve_priv,
261 : unsigned int attributes_length,
262 : const char *attributes[const static attributes_length])
263 1 : {
264 : struct TALER_EXCHANGE_PostReservesAttestHandle *prah;
265 : struct TALER_ReserveSignatureP reserve_sig;
266 : json_t *details;
267 : struct GNUNET_TIME_Timestamp ts;
268 :
269 1 : if (0 == attributes_length)
270 : {
271 0 : GNUNET_break (0);
272 0 : return NULL;
273 : }
274 1 : details = json_array ();
275 1 : GNUNET_assert (NULL != details);
276 2 : for (unsigned int i = 0; i < attributes_length; i++)
277 : {
278 1 : GNUNET_assert (0 ==
279 : json_array_append_new (details,
280 : json_string (attributes[i])));
281 : }
282 1 : prah = GNUNET_new (struct TALER_EXCHANGE_PostReservesAttestHandle);
283 1 : prah->ctx = ctx;
284 1 : prah->base_url = GNUNET_strdup (url);
285 1 : prah->keys = TALER_EXCHANGE_keys_incref (keys);
286 1 : GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
287 : &prah->reserve_pub.eddsa_pub);
288 1 : ts = GNUNET_TIME_timestamp_get ();
289 1 : TALER_wallet_reserve_attest_request_sign (ts,
290 : details,
291 : reserve_priv,
292 : &reserve_sig);
293 1 : prah->body = GNUNET_JSON_PACK (
294 : GNUNET_JSON_pack_data_auto ("reserve_sig",
295 : &reserve_sig),
296 : GNUNET_JSON_pack_timestamp ("request_timestamp",
297 : ts),
298 : GNUNET_JSON_pack_array_steal ("details",
299 : details));
300 1 : if (NULL == prah->body)
301 : {
302 0 : GNUNET_break (0);
303 0 : GNUNET_free (prah->base_url);
304 0 : TALER_EXCHANGE_keys_decref (prah->keys);
305 0 : GNUNET_free (prah);
306 0 : return NULL;
307 : }
308 1 : return prah;
309 : }
310 :
311 :
312 : enum TALER_ErrorCode
313 1 : TALER_EXCHANGE_post_reserves_attest_start (
314 : struct TALER_EXCHANGE_PostReservesAttestHandle *prah,
315 : TALER_EXCHANGE_PostReservesAttestCallback cb,
316 : TALER_EXCHANGE_POST_RESERVES_ATTEST_RESULT_CLOSURE *cb_cls)
317 : {
318 : CURL *eh;
319 : char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
320 :
321 1 : prah->cb = cb;
322 1 : prah->cb_cls = cb_cls;
323 : {
324 : char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
325 : char *end;
326 :
327 1 : end = GNUNET_STRINGS_data_to_string (
328 1 : &prah->reserve_pub,
329 : sizeof (prah->reserve_pub),
330 : pub_str,
331 : sizeof (pub_str));
332 1 : *end = '\0';
333 1 : GNUNET_snprintf (arg_str,
334 : sizeof (arg_str),
335 : "reserves/%s/attest",
336 : pub_str);
337 : }
338 1 : prah->url = TALER_url_join (prah->base_url,
339 : arg_str,
340 : NULL);
341 1 : if (NULL == prah->url)
342 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
343 1 : eh = TALER_EXCHANGE_curl_easy_get_ (prah->url);
344 2 : if ( (NULL == eh) ||
345 : (GNUNET_OK !=
346 1 : TALER_curl_easy_post (&prah->post_ctx,
347 : eh,
348 1 : prah->body)) )
349 : {
350 0 : GNUNET_break (0);
351 0 : if (NULL != eh)
352 0 : curl_easy_cleanup (eh);
353 0 : GNUNET_free (prah->url);
354 0 : prah->url = NULL;
355 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
356 : }
357 2 : prah->job = GNUNET_CURL_job_add2 (prah->ctx,
358 : eh,
359 1 : prah->post_ctx.headers,
360 : &handle_reserves_attest_finished,
361 : prah);
362 1 : if (NULL == prah->job)
363 : {
364 0 : TALER_curl_easy_post_finished (&prah->post_ctx);
365 0 : GNUNET_free (prah->url);
366 0 : prah->url = NULL;
367 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
368 : }
369 1 : return TALER_EC_NONE;
370 : }
371 :
372 :
373 : void
374 1 : TALER_EXCHANGE_post_reserves_attest_cancel (
375 : struct TALER_EXCHANGE_PostReservesAttestHandle *prah)
376 : {
377 1 : if (NULL != prah->job)
378 : {
379 0 : GNUNET_CURL_job_cancel (prah->job);
380 0 : prah->job = NULL;
381 : }
382 1 : TALER_curl_easy_post_finished (&prah->post_ctx);
383 1 : json_decref (prah->body);
384 1 : GNUNET_free (prah->url);
385 1 : GNUNET_free (prah->base_url);
386 1 : TALER_EXCHANGE_keys_decref (prah->keys);
387 1 : GNUNET_free (prah);
388 1 : }
389 :
390 :
391 : /* end of exchange_api_post-reserves-RESERVE_PUB-attest.c */
|