exchange_api_post-reserves-RESERVE_PUB-attest.c (11560B)
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 handle_reserves_attest_ok (struct TALER_EXCHANGE_PostReservesAttestHandle *prah, 103 const json_t *j) 104 { 105 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 GNUNET_JSON_spec_timestamp ("exchange_timestamp", 112 &rs.details.ok.exchange_time), 113 GNUNET_JSON_spec_timestamp ("expiration_time", 114 &rs.details.ok.expiration_time), 115 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 116 &rs.details.ok.exchange_sig), 117 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 118 &rs.details.ok.exchange_pub), 119 GNUNET_JSON_spec_object_const ("attributes", 120 &attributes), 121 GNUNET_JSON_spec_end () 122 }; 123 124 if (GNUNET_OK != 125 GNUNET_JSON_parse (j, 126 spec, 127 NULL, 128 NULL)) 129 { 130 GNUNET_break_op (0); 131 return GNUNET_SYSERR; 132 } 133 if (GNUNET_OK != 134 TALER_EXCHANGE_test_signing_key (prah->keys, 135 &rs.details.ok.exchange_pub)) 136 { 137 GNUNET_break_op (0); 138 rs.hr.http_status = 0; 139 rs.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE; 140 prah->cb (prah->cb_cls, 141 &rs); 142 prah->cb = NULL; 143 GNUNET_JSON_parse_free (spec); 144 return GNUNET_SYSERR; 145 } 146 rs.details.ok.attributes = attributes; 147 if (GNUNET_OK != 148 TALER_exchange_online_reserve_attest_details_verify ( 149 rs.details.ok.exchange_time, 150 rs.details.ok.expiration_time, 151 &prah->reserve_pub, 152 attributes, 153 &rs.details.ok.exchange_pub, 154 &rs.details.ok.exchange_sig)) 155 { 156 GNUNET_break_op (0); 157 GNUNET_JSON_parse_free (spec); 158 return GNUNET_SYSERR; 159 } 160 prah->cb (prah->cb_cls, 161 &rs); 162 prah->cb = NULL; 163 GNUNET_JSON_parse_free (spec); 164 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 handle_reserves_attest_finished (void *cls, 178 long response_code, 179 const void *response) 180 { 181 struct TALER_EXCHANGE_PostReservesAttestHandle *prah = cls; 182 const json_t *j = response; 183 struct TALER_EXCHANGE_PostReservesAttestResponse rs = { 184 .hr.reply = j, 185 .hr.http_status = (unsigned int) response_code 186 }; 187 188 prah->job = NULL; 189 switch (response_code) 190 { 191 case 0: 192 rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 193 break; 194 case MHD_HTTP_OK: 195 if (GNUNET_OK != 196 handle_reserves_attest_ok (prah, 197 j)) 198 { 199 rs.hr.http_status = 0; 200 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 201 } 202 break; 203 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 GNUNET_break (0); 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_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 GNUNET_break (0); 214 rs.hr.ec = TALER_JSON_get_error_code (j); 215 rs.hr.hint = TALER_JSON_get_error_hint (j); 216 break; 217 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 rs.hr.ec = TALER_JSON_get_error_code (j); 221 rs.hr.hint = TALER_JSON_get_error_hint (j); 222 break; 223 case MHD_HTTP_CONFLICT: 224 /* Server doesn't have the requested attributes */ 225 rs.hr.ec = TALER_JSON_get_error_code (j); 226 rs.hr.hint = TALER_JSON_get_error_hint (j); 227 break; 228 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 rs.hr.ec = TALER_JSON_get_error_code (j); 232 rs.hr.hint = TALER_JSON_get_error_hint (j); 233 break; 234 default: 235 /* unexpected response code */ 236 GNUNET_break_op (0); 237 rs.hr.ec = TALER_JSON_get_error_code (j); 238 rs.hr.hint = TALER_JSON_get_error_hint (j); 239 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 break; 244 } 245 if (NULL != prah->cb) 246 { 247 prah->cb (prah->cb_cls, 248 &rs); 249 prah->cb = NULL; 250 } 251 TALER_EXCHANGE_post_reserves_attest_cancel (prah); 252 } 253 254 255 struct TALER_EXCHANGE_PostReservesAttestHandle * 256 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 { 264 struct TALER_EXCHANGE_PostReservesAttestHandle *prah; 265 struct TALER_ReserveSignatureP reserve_sig; 266 json_t *details; 267 struct GNUNET_TIME_Timestamp ts; 268 269 if (0 == attributes_length) 270 { 271 GNUNET_break (0); 272 return NULL; 273 } 274 details = json_array (); 275 GNUNET_assert (NULL != details); 276 for (unsigned int i = 0; i < attributes_length; i++) 277 { 278 GNUNET_assert (0 == 279 json_array_append_new (details, 280 json_string (attributes[i]))); 281 } 282 prah = GNUNET_new (struct TALER_EXCHANGE_PostReservesAttestHandle); 283 prah->ctx = ctx; 284 prah->base_url = GNUNET_strdup (url); 285 prah->keys = TALER_EXCHANGE_keys_incref (keys); 286 GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, 287 &prah->reserve_pub.eddsa_pub); 288 ts = GNUNET_TIME_timestamp_get (); 289 TALER_wallet_reserve_attest_request_sign (ts, 290 details, 291 reserve_priv, 292 &reserve_sig); 293 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 if (NULL == prah->body) 301 { 302 GNUNET_break (0); 303 GNUNET_free (prah->base_url); 304 TALER_EXCHANGE_keys_decref (prah->keys); 305 GNUNET_free (prah); 306 return NULL; 307 } 308 return prah; 309 } 310 311 312 enum TALER_ErrorCode 313 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 prah->cb = cb; 322 prah->cb_cls = cb_cls; 323 { 324 char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; 325 char *end; 326 327 end = GNUNET_STRINGS_data_to_string ( 328 &prah->reserve_pub, 329 sizeof (prah->reserve_pub), 330 pub_str, 331 sizeof (pub_str)); 332 *end = '\0'; 333 GNUNET_snprintf (arg_str, 334 sizeof (arg_str), 335 "reserves/%s/attest", 336 pub_str); 337 } 338 prah->url = TALER_url_join (prah->base_url, 339 arg_str, 340 NULL); 341 if (NULL == prah->url) 342 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 343 eh = TALER_EXCHANGE_curl_easy_get_ (prah->url); 344 if ( (NULL == eh) || 345 (GNUNET_OK != 346 TALER_curl_easy_post (&prah->post_ctx, 347 eh, 348 prah->body)) ) 349 { 350 GNUNET_break (0); 351 if (NULL != eh) 352 curl_easy_cleanup (eh); 353 GNUNET_free (prah->url); 354 prah->url = NULL; 355 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 356 } 357 prah->job = GNUNET_CURL_job_add2 (prah->ctx, 358 eh, 359 prah->post_ctx.headers, 360 &handle_reserves_attest_finished, 361 prah); 362 if (NULL == prah->job) 363 { 364 TALER_curl_easy_post_finished (&prah->post_ctx); 365 GNUNET_free (prah->url); 366 prah->url = NULL; 367 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 368 } 369 return TALER_EC_NONE; 370 } 371 372 373 void 374 TALER_EXCHANGE_post_reserves_attest_cancel ( 375 struct TALER_EXCHANGE_PostReservesAttestHandle *prah) 376 { 377 if (NULL != prah->job) 378 { 379 GNUNET_CURL_job_cancel (prah->job); 380 prah->job = NULL; 381 } 382 TALER_curl_easy_post_finished (&prah->post_ctx); 383 json_decref (prah->body); 384 GNUNET_free (prah->url); 385 GNUNET_free (prah->base_url); 386 TALER_EXCHANGE_keys_decref (prah->keys); 387 GNUNET_free (prah); 388 } 389 390 391 /* end of exchange_api_post-reserves-RESERVE_PUB-attest.c */