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