exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit 5dbf9d7cf1b79cdbcb09b96d5745437b9ef4aea9
parent a467cb23e3d5067a0e4aac6f9022af3fd883325c
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat,  4 Apr 2026 13:26:57 +0200

change /reserves-attest/RPUB to /reserves/RPUB/attest for consistency

Diffstat:
Msrc/exchange/Makefile.am | 4++--
Msrc/exchange/taler-exchange-httpd.c | 40++++++++++++++++++++++------------------
Asrc/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB-attest.c | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB-attest.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/exchange/taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.c | 195-------------------------------------------------------------------------------
Dsrc/exchange/taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.h | 44--------------------------------------------
Asrc/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-attest.c | 384+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-attest.h | 42++++++++++++++++++++++++++++++++++++++++++
Dsrc/exchange/taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.c | 391-------------------------------------------------------------------------------
Dsrc/exchange/taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.h | 41-----------------------------------------
Msrc/exchange/taler-exchange-httpd_post-reveal-melt.c | 154+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/exchange/taler-exchange-httpd_post-reveal-withdraw.c | 73++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/exchange/taler-exchange-httpd_post-withdraw.c | 24++++++++++++++++--------
Msrc/lib/Makefile.am | 4++--
Asrc/lib/exchange_api_get-reserves-RESERVE_PUB-attest.c | 296+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/exchange_api_get-reserves-attest-RESERVE_PUB.c | 296-------------------------------------------------------------------------------
Asrc/lib/exchange_api_post-reserves-RESERVE_PUB-attest.c | 393+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/exchange_api_post-reserves-attest-RESERVE_PUB.c | 393-------------------------------------------------------------------------------
Msrc/lib/exchange_api_post-withdraw.c | 12++++--------
Msrc/lib/exchange_api_post-withdraw_blinded.c | 26++++++++++++++++++++++++++
20 files changed, 1542 insertions(+), 1493 deletions(-)

diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am @@ -197,10 +197,10 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_post-recoup-withdraw.c taler-exchange-httpd_post-recoup-withdraw.h \ taler-exchange-httpd_post-recoup-refresh.c taler-exchange-httpd_post-recoup-refresh.h \ taler-exchange-httpd_post-coins-COIN_PUB-refund.c taler-exchange-httpd_post-coins-COIN_PUB-refund.h \ - taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.c taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.h \ + taler-exchange-httpd_post-reserves-RESERVE_PUB-attest.c taler-exchange-httpd_post-reserves-RESERVE_PUB-attest.h \ taler-exchange-httpd_post-reserves-RESERVE_PUB-close.c taler-exchange-httpd_post-reserves-RESERVE_PUB-close.h \ taler-exchange-httpd_get-reserves-RESERVE_PUB.c taler-exchange-httpd_get-reserves-RESERVE_PUB.h \ - taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.c taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.h \ + taler-exchange-httpd_get-reserves-RESERVE_PUB-attest.c taler-exchange-httpd_get-reserves-RESERVE_PUB-attest.h \ taler-exchange-httpd_get-reserves-RESERVE_PUB-history.c taler-exchange-httpd_get-reserves-RESERVE_PUB-history.h \ taler-exchange-httpd_post-reserves-RESERVE_PUB-open.c taler-exchange-httpd_post-reserves-RESERVE_PUB-open.h \ taler-exchange-httpd_post-reserves-RESERVE_PUB-purse.c taler-exchange-httpd_post-reserves-RESERVE_PUB-purse.h \ diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c @@ -69,10 +69,10 @@ #include "taler-exchange-httpd_post-recoup-withdraw.h" #include "taler-exchange-httpd_post-recoup-refresh.h" #include "taler-exchange-httpd_post-coins-COIN_PUB-refund.h" -#include "taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.h" +#include "taler-exchange-httpd_post-reserves-RESERVE_PUB-attest.h" #include "taler-exchange-httpd_post-reserves-RESERVE_PUB-close.h" #include "taler-exchange-httpd_get-reserves-RESERVE_PUB.h" -#include "taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.h" +#include "taler-exchange-httpd_get-reserves-RESERVE_PUB-attest.h" #include "taler-exchange-httpd_get-reserves-RESERVE_PUB-history.h" #include "taler-exchange-httpd_post-reserves-RESERVE_PUB-open.h" #include "taler-exchange-httpd_post-reserves-RESERVE_PUB-purse.h" @@ -657,15 +657,23 @@ handle_get_aml (struct TEH_RequestContext *rc, GNUNET_STRINGS_string_to_data (sig_hdr, strlen (sig_hdr), &officer_sig, - sizeof (officer_sig))) || - (GNUNET_OK != - TALER_officer_aml_query_verify (&officer_pub, - &officer_sig)) ) + sizeof (officer_sig))) ) { GNUNET_break_op (0); return TALER_MHD_reply_with_error ( rc->connection, MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED, + TALER_AML_OFFICER_SIGNATURE_HEADER); + } + if (GNUNET_OK != + TALER_officer_aml_query_verify (&officer_pub, + &officer_sig)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_FORBIDDEN, TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_GET_SIGNATURE_INVALID, sig_hdr); } @@ -763,6 +771,10 @@ handle_post_reserves (struct TEH_RequestContext *rc, .handler = &TEH_handler_reserves_close }, { + .op = "attest", + .handler = &TEH_handler_reserves_attest + }, + { .op = NULL, .handler = NULL }, @@ -838,6 +850,10 @@ handle_get_reserves (struct TEH_RequestContext *rc, .handler = &TEH_handler_reserves_history }, { + .op = "attest", + .handler = &TEH_handler_get_reserves_attest + }, + { .op = NULL, .handler = NULL }, @@ -1691,18 +1707,6 @@ handle_mhd_request (void *cls, .handler.post = &TEH_handler_reveal_melt, .nargs = 0 }, - { - .url = "reserves-attest", - .method = MHD_HTTP_METHOD_GET, - .handler.get = &TEH_handler_reserves_get_attest, - .nargs = 1 - }, - { - .url = "reserves-attest", - .method = MHD_HTTP_METHOD_POST, - .handler.post = &TEH_handler_reserves_attest, - .nargs = 1 - }, /* coins */ { .url = "coins", diff --git a/src/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB-attest.c b/src/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB-attest.c @@ -0,0 +1,177 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-exchange-httpd_get-reserves-RESERVE_PUB-attest.c + * @brief Handle GET /reserves/$RESERVE_PUB/attest requests + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include "taler/taler_kyclogic_lib.h" +#include "taler/taler_mhd_lib.h" +#include "taler/taler_json_lib.h" +#include "taler/taler_dbevents.h" +#include "taler-exchange-httpd_get-keys.h" +#include "taler-exchange-httpd_get-reserves-RESERVE_PUB-attest.h" +#include "taler-exchange-httpd_responses.h" + + +/** + * Closure for #reserve_attest_transaction. + */ +struct ReserveAttestContext +{ + + /** + * Hash of the payto URI of this reserve. + */ + struct TALER_NormalizedPaytoHashP h_payto; + + /** + * Available attributes. + */ + json_t *attributes; + +}; + + +/** + * Function called with information about all applicable + * legitimization processes for the given user. + * + * @param cls our `struct ReserveAttestContext *` + * @param h_payto account for which the attribute data is stored + * @param provider_name provider that must be checked + * @param collection_time when was the data collected + * @param expiration_time when does the data expire + * @param enc_attributes_size number of bytes in @a enc_attributes + * @param enc_attributes encrypted attribute data + */ +static void +kyc_process_cb (void *cls, + const struct TALER_NormalizedPaytoHashP *h_payto, + const char *provider_name, + struct GNUNET_TIME_Timestamp collection_time, + struct GNUNET_TIME_Timestamp expiration_time, + size_t enc_attributes_size, + const void *enc_attributes) +{ + struct ReserveAttestContext *rsc = cls; + json_t *attrs; + json_t *val; + const char *name; + + if (GNUNET_TIME_absolute_is_past ( + expiration_time.abs_time)) + return; + attrs = TALER_CRYPTO_kyc_attributes_decrypt ( + &TEH_attribute_key, + enc_attributes, + enc_attributes_size); + json_object_foreach (attrs, name, val) + { + bool duplicate = false; + size_t idx; + json_t *str; + + json_array_foreach (rsc->attributes, idx, str) + { + if (0 == strcmp (json_string_value (str), + name)) + { + duplicate = true; + break; + } + } + if (duplicate) + continue; + GNUNET_assert (0 == + json_array_append_new (rsc->attributes, + json_string (name))); + } + json_decref (attrs); +} + + +MHD_RESULT +TEH_handler_get_reserves_attest ( + struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub) +{ + struct ReserveAttestContext rsc = { + .attributes = NULL + }; + + { + struct TALER_NormalizedPayto payto_uri; + + payto_uri + = TALER_reserve_make_payto (TEH_base_url, + reserve_pub); + TALER_normalized_payto_hash (payto_uri, + &rsc.h_payto); + GNUNET_free (payto_uri.normalized_payto); + } + { + enum GNUNET_DB_QueryStatus qs; + + rsc.attributes = json_array (); + GNUNET_assert (NULL != rsc.attributes); + qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls, + &rsc.h_payto, + &kyc_process_cb, + &rsc); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + json_decref (rsc.attributes); + rsc.attributes = NULL; + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "select_kyc_attributes"); + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + json_decref (rsc.attributes); + rsc.attributes = NULL; + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "select_kyc_attributes"); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_break_op (0); + json_decref (rsc.attributes); + rsc.attributes = NULL; + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, + NULL); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + } + return TALER_MHD_REPLY_JSON_PACK ( + rc->connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_array_steal ("details", + rsc.attributes)); +} + + +/* end of taler-exchange-httpd_get-reserves-RESERVE_PUB-attest.c */ diff --git a/src/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB-attest.h b/src/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB-attest.h @@ -0,0 +1,46 @@ +/* + This file is part of TALER + Copyright (C) 2014-2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-exchange-httpd_get-reserves-RESERVE_PUB-attest.h + * @brief Handle /reserves/$RESERVE_PUB GET_ATTEST requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_GET_RESERVES_RESERVE_PUB_ATTEST_H +#define TALER_EXCHANGE_HTTPD_GET_RESERVES_RESERVE_PUB_ATTEST_H + +#include <microhttpd.h> +#include "taler-exchange-httpd.h" + + +/** + * Handle a GET "/reserves/$RID/attest" request. Parses the + * given "reserve_pub" in @a args (which should contain the + * EdDSA public key of a reserve) and then responds with the + * available attestations for the reserve. + * + * @param rc request context + * @param reserve_pub public key of the reserve to get available + * attributes for + * @return MHD result code + */ +MHD_RESULT +TEH_handler_get_reserves_attest ( + struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub); + +#endif diff --git a/src/exchange/taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.c b/src/exchange/taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.c @@ -1,195 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.c - * @brief Handle GET /reserves/$RESERVE_PUB/attest requests - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <gnunet/gnunet_util_lib.h> -#include <jansson.h> -#include "taler/taler_kyclogic_lib.h" -#include "taler/taler_mhd_lib.h" -#include "taler/taler_json_lib.h" -#include "taler/taler_dbevents.h" -#include "taler-exchange-httpd_get-keys.h" -#include "taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.h" -#include "taler-exchange-httpd_responses.h" - - -/** - * Closure for #reserve_attest_transaction. - */ -struct ReserveAttestContext -{ - /** - * Public key of the reserve the inquiry is about. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Hash of the payto URI of this reserve. - */ - struct TALER_NormalizedPaytoHashP h_payto; - - /** - * Available attributes. - */ - json_t *attributes; - -}; - - -/** - * Function called with information about all applicable - * legitimization processes for the given user. - * - * @param cls our `struct ReserveAttestContext *` - * @param h_payto account for which the attribute data is stored - * @param provider_name provider that must be checked - * @param collection_time when was the data collected - * @param expiration_time when does the data expire - * @param enc_attributes_size number of bytes in @a enc_attributes - * @param enc_attributes encrypted attribute data - */ -static void -kyc_process_cb (void *cls, - const struct TALER_NormalizedPaytoHashP *h_payto, - const char *provider_name, - struct GNUNET_TIME_Timestamp collection_time, - struct GNUNET_TIME_Timestamp expiration_time, - size_t enc_attributes_size, - const void *enc_attributes) -{ - struct ReserveAttestContext *rsc = cls; - json_t *attrs; - json_t *val; - const char *name; - - if (GNUNET_TIME_absolute_is_past ( - expiration_time.abs_time)) - return; - attrs = TALER_CRYPTO_kyc_attributes_decrypt ( - &TEH_attribute_key, - enc_attributes, - enc_attributes_size); - json_object_foreach (attrs, name, val) - { - bool duplicate = false; - size_t idx; - json_t *str; - - json_array_foreach (rsc->attributes, idx, str) - { - if (0 == strcmp (json_string_value (str), - name)) - { - duplicate = true; - break; - } - } - if (duplicate) - continue; - GNUNET_assert (0 == - json_array_append_new (rsc->attributes, - json_string (name))); - } - json_decref (attrs); -} - - -MHD_RESULT -TEH_handler_reserves_get_attest ( - struct TEH_RequestContext *rc, - const char *const args[1]) -{ - struct ReserveAttestContext rsc = { - .attributes = NULL - }; - - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data ( - args[0], - strlen (args[0]), - &rsc.reserve_pub, - sizeof (rsc.reserve_pub))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error ( - rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, - args[0]); - } - { - struct TALER_NormalizedPayto payto_uri; - - payto_uri - = TALER_reserve_make_payto (TEH_base_url, - &rsc.reserve_pub); - TALER_normalized_payto_hash (payto_uri, - &rsc.h_payto); - GNUNET_free (payto_uri.normalized_payto); - } - { - enum GNUNET_DB_QueryStatus qs; - - rsc.attributes = json_array (); - GNUNET_assert (NULL != rsc.attributes); - qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls, - &rsc.h_payto, - &kyc_process_cb, - &rsc); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - GNUNET_break (0); - json_decref (rsc.attributes); - rsc.attributes = NULL; - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "select_kyc_attributes"); - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - json_decref (rsc.attributes); - rsc.attributes = NULL; - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "select_kyc_attributes"); - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - GNUNET_break_op (0); - json_decref (rsc.attributes); - rsc.attributes = NULL; - return TALER_MHD_reply_with_error ( - rc->connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, - NULL); - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - break; - } - } - return TALER_MHD_REPLY_JSON_PACK ( - rc->connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_array_steal ("details", - rsc.attributes)); -} - - -/* end of taler-exchange-httpd_reserves_get_attest.c */ diff --git a/src/exchange/taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.h b/src/exchange/taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.h @@ -1,44 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.h - * @brief Handle /reserves/$RESERVE_PUB GET_ATTEST requests - * @author Florian Dold - * @author Benedikt Mueller - * @author Christian Grothoff - */ -#ifndef TALER_EXCHANGE_HTTPD_GET_RESERVES_ATTEST_RESERVE_PUB_H -#define TALER_EXCHANGE_HTTPD_GET_RESERVES_ATTEST_RESERVE_PUB_H - -#include <microhttpd.h> -#include "taler-exchange-httpd.h" - - -/** - * Handle a GET "/reserves/$RID/attest" request. Parses the - * given "reserve_pub" in @a args (which should contain the - * EdDSA public key of a reserve) and then responds with the - * available attestations for the reserve. - * - * @param rc request context - * @param args array of additional options (length: 1, just the reserve_pub) - * @return MHD result code - */ -MHD_RESULT -TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc, - const char *const args[1]); - -#endif diff --git a/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-attest.c b/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-attest.c @@ -0,0 +1,384 @@ +/* + This file is part of TALER + Copyright (C) 2014-2022, 2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-exchange-httpd_post-reserves-RESERVE_PUB-attest.c + * @brief Handle /reserves/$RESERVE_PUB/attest requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include "taler/taler_dbevents.h" +#include "taler/taler_kyclogic_lib.h" +#include "taler/taler_json_lib.h" +#include "taler/taler_mhd_lib.h" +#include "taler-exchange-httpd_get-keys.h" +#include "taler-exchange-httpd_post-reserves-RESERVE_PUB-attest.h" +#include "taler-exchange-httpd_responses.h" + + +/** + * How far do we allow a client's time to be off when + * checking the request timestamp? + */ +#define TIMESTAMP_TOLERANCE \ + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15) + + +/** + * Closure for #reserve_attest_transaction. + */ +struct ReserveAttestContext +{ + /** + * Public key of the reserve the inquiry is about. + */ + const struct TALER_ReservePublicKeyP *reserve_pub; + + /** + * Hash of the payto URI of this reserve. + */ + struct TALER_NormalizedPaytoHashP h_payto; + + /** + * Timestamp of the request. + */ + struct GNUNET_TIME_Timestamp timestamp; + + /** + * Expiration time for the attestation. + */ + struct GNUNET_TIME_Timestamp etime; + + /** + * List of requested details. + */ + const json_t *details; + + /** + * Client signature approving the request. + */ + struct TALER_ReserveSignatureP reserve_sig; + + /** + * Attributes we are affirming. JSON object. + */ + json_t *json_attest; + + /** + * Database error codes encountered. + */ + enum GNUNET_DB_QueryStatus qs; + + /** + * Set to true if we did not find the reserve. + */ + bool not_found; + +}; + + +/** + * Send reserve attest to client. + * + * @param connection connection to the client + * @param rhc reserve attest to return + * @return MHD result code + */ +static MHD_RESULT +reply_reserve_attest_success (struct MHD_Connection *connection, + const struct ReserveAttestContext *rhc) +{ + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_ExchangePublicKeyP exchange_pub; + enum TALER_ErrorCode ec; + struct GNUNET_TIME_Timestamp now; + + if (NULL == rhc->json_attest) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE, + NULL); + } + now = GNUNET_TIME_timestamp_get (); + ec = TALER_exchange_online_reserve_attest_details_sign ( + &TEH_keys_exchange_sign_, + now, + rhc->etime, + rhc->reserve_pub, + rhc->json_attest, + &exchange_pub, + &exchange_sig); + if (TALER_EC_NONE != ec) + { + GNUNET_break (0); + return TALER_MHD_reply_with_ec (connection, + ec, + NULL); + } + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_data_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_pack_data_auto ("exchange_pub", + &exchange_pub), + GNUNET_JSON_pack_timestamp ("exchange_timestamp", + now), + GNUNET_JSON_pack_timestamp ("expiration_time", + rhc->etime), + GNUNET_JSON_pack_object_steal ("attributes", + rhc->json_attest)); +} + + +/** + * Function called with information about all applicable + * legitimization processes for the given user. Finds the + * available attributes and merges them into our result + * set based on the details requested by the client. + * + * @param cls our `struct ReserveAttestContext *` + * @param h_payto account for which the attribute data is stored + * @param provider_name provider that must be checked + * @param collection_time when was the data collected + * @param expiration_time when does the data expire + * @param enc_attributes_size number of bytes in @a enc_attributes + * @param enc_attributes encrypted attribute data + */ +static void +kyc_process_cb (void *cls, + const struct TALER_NormalizedPaytoHashP *h_payto, + const char *provider_name, + struct GNUNET_TIME_Timestamp collection_time, + struct GNUNET_TIME_Timestamp expiration_time, + size_t enc_attributes_size, + const void *enc_attributes) +{ + struct ReserveAttestContext *rsc = cls; + json_t *attrs; + json_t *val; + const char *name; + bool match = false; + + if (GNUNET_TIME_absolute_is_past (expiration_time.abs_time)) + return; + attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key, + enc_attributes, + enc_attributes_size); + if (NULL == attrs) + { + GNUNET_break (0); + return; + } + json_object_foreach (attrs, name, val) + { + bool requested = strcmp (name, + "FORM_ID"); /* we always return the FORM_ID */ + size_t idx; + json_t *str; + + if (NULL != json_object_get (rsc->json_attest, + name)) + continue; /* duplicate */ + json_array_foreach (rsc->details, idx, str) + { + if (0 == strcmp (json_string_value (str), + name)) + { + requested = true; + break; + } + } + if (! requested) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Skipping attribute `%s': not requested\n", + name); + continue; + } + match = true; + GNUNET_assert (0 == + json_object_set (rsc->json_attest, /* NOT set_new! */ + name, + val)); + } + json_decref (attrs); + if (! match) + return; + rsc->etime = GNUNET_TIME_timestamp_min (expiration_time, + rsc->etime); +} + + +/** + * Function implementing /reserves/$RID/attest transaction. Given the public + * key of a reserve, return the associated transaction attest. Runs the + * transaction logic; IF it returns a non-error code, the transaction logic + * MUST NOT queue a MHD response. IF it returns an hard error, the + * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it + * returns the soft error code, the function MAY be called again to retry and + * MUST not queue a MHD response. + * + * @param cls a `struct ReserveAttestContext *` + * @param connection MHD request which triggered the transaction + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!); unused + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +reserve_attest_transaction (void *cls, + struct MHD_Connection *connection, + MHD_RESULT *mhd_ret) +{ + struct ReserveAttestContext *rsc = cls; + enum GNUNET_DB_QueryStatus qs; + + rsc->json_attest = json_object (); + GNUNET_assert (NULL != rsc->json_attest); + qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls, + &rsc->h_payto, + &kyc_process_cb, + rsc); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + *mhd_ret + = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "select_kyc_attributes"); + return qs; + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return qs; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + rsc->not_found = true; + return qs; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + rsc->not_found = false; + break; + } + return qs; +} + + +MHD_RESULT +TEH_handler_reserves_attest ( + struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *root) +{ + struct ReserveAttestContext rsc = { + .etime = GNUNET_TIME_UNIT_FOREVER_TS, + .reserve_pub = reserve_pub + }; + MHD_RESULT mhd_ret; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ("request_timestamp", + &rsc.timestamp), + GNUNET_JSON_spec_array_const ("details", + &rsc.details), + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &rsc.reserve_sig), + GNUNET_JSON_spec_end () + }; + struct GNUNET_TIME_Timestamp now; + + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (rc->connection, + root, + spec); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return MHD_NO; /* hard failure */ + } + if (GNUNET_NO == res) + { + GNUNET_break_op (0); + return MHD_YES; /* failure */ + } + } + now = GNUNET_TIME_timestamp_get (); + if (! GNUNET_TIME_absolute_approx_eq (now.abs_time, + rsc.timestamp.abs_time, + TIMESTAMP_TOLERANCE)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW, + NULL); + } + + if (GNUNET_OK != + TALER_wallet_reserve_attest_request_verify (rsc.timestamp, + rsc.details, + rsc.reserve_pub, + &rsc.reserve_sig)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_RESERVES_ATTEST_BAD_SIGNATURE, + NULL); + } + + { + struct TALER_NormalizedPayto payto_uri; + + payto_uri = TALER_reserve_make_payto (TEH_base_url, + rsc.reserve_pub); + TALER_normalized_payto_hash (payto_uri, + &rsc.h_payto); + GNUNET_free (payto_uri.normalized_payto); + } + + if (GNUNET_OK != + TEH_DB_run_transaction (rc->connection, + "post reserve attest", + TEH_MT_REQUEST_OTHER, + &mhd_ret, + &reserve_attest_transaction, + &rsc)) + { + return mhd_ret; + } + if (rsc.not_found) + { + json_decref (rsc.json_attest); + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, + NULL); + } + return reply_reserve_attest_success (rc->connection, + &rsc); +} + + +/* end of taler-exchange-httpd_reserves-RESERVE_PUB-attest.c */ diff --git a/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-attest.h b/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-attest.h @@ -0,0 +1,42 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-exchange-httpd_post-reserves-RESERVE_PUB-attest.h + * @brief Handle /reserves/$RESERVE_PUB/attest requests + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_POST_RESERVES_RESERVE_PUB_ATTEST_H +#define TALER_EXCHANGE_HTTPD_POST_RESERVES_RESERVE_PUB_ATTEST_H + +#include <microhttpd.h> +#include "taler-exchange-httpd.h" + + +/** + * Handle a POST "/reserves/$RID/attest" request. + * + * @param rc request context + * @param reserve_pub public key of the reserve + * @param root uploaded body from the client + * @return MHD result code + */ +MHD_RESULT +TEH_handler_reserves_attest ( + struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *root); + +#endif diff --git a/src/exchange/taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.c b/src/exchange/taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.c @@ -1,391 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2022, 2024 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.c - * @brief Handle /reserves/$RESERVE_PUB/attest requests - * @author Florian Dold - * @author Benedikt Mueller - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <gnunet/gnunet_util_lib.h> -#include <jansson.h> -#include "taler/taler_dbevents.h" -#include "taler/taler_kyclogic_lib.h" -#include "taler/taler_json_lib.h" -#include "taler/taler_mhd_lib.h" -#include "taler-exchange-httpd_get-keys.h" -#include "taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.h" -#include "taler-exchange-httpd_responses.h" - - -/** - * How far do we allow a client's time to be off when - * checking the request timestamp? - */ -#define TIMESTAMP_TOLERANCE \ - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15) - - -/** - * Closure for #reserve_attest_transaction. - */ -struct ReserveAttestContext -{ - /** - * Public key of the reserve the inquiry is about. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Hash of the payto URI of this reserve. - */ - struct TALER_NormalizedPaytoHashP h_payto; - - /** - * Timestamp of the request. - */ - struct GNUNET_TIME_Timestamp timestamp; - - /** - * Expiration time for the attestation. - */ - struct GNUNET_TIME_Timestamp etime; - - /** - * List of requested details. - */ - const json_t *details; - - /** - * Client signature approving the request. - */ - struct TALER_ReserveSignatureP reserve_sig; - - /** - * Attributes we are affirming. JSON object. - */ - json_t *json_attest; - - /** - * Database error codes encountered. - */ - enum GNUNET_DB_QueryStatus qs; - - /** - * Set to true if we did not find the reserve. - */ - bool not_found; - -}; - - -/** - * Send reserve attest to client. - * - * @param connection connection to the client - * @param rhc reserve attest to return - * @return MHD result code - */ -static MHD_RESULT -reply_reserve_attest_success (struct MHD_Connection *connection, - const struct ReserveAttestContext *rhc) -{ - struct TALER_ExchangeSignatureP exchange_sig; - struct TALER_ExchangePublicKeyP exchange_pub; - enum TALER_ErrorCode ec; - struct GNUNET_TIME_Timestamp now; - - if (NULL == rhc->json_attest) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE, - NULL); - } - now = GNUNET_TIME_timestamp_get (); - ec = TALER_exchange_online_reserve_attest_details_sign ( - &TEH_keys_exchange_sign_, - now, - rhc->etime, - &rhc->reserve_pub, - rhc->json_attest, - &exchange_pub, - &exchange_sig); - if (TALER_EC_NONE != ec) - { - GNUNET_break (0); - return TALER_MHD_reply_with_ec (connection, - ec, - NULL); - } - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_data_auto ("exchange_sig", - &exchange_sig), - GNUNET_JSON_pack_data_auto ("exchange_pub", - &exchange_pub), - GNUNET_JSON_pack_timestamp ("exchange_timestamp", - now), - GNUNET_JSON_pack_timestamp ("expiration_time", - rhc->etime), - GNUNET_JSON_pack_object_steal ("attributes", - rhc->json_attest)); -} - - -/** - * Function called with information about all applicable - * legitimization processes for the given user. Finds the - * available attributes and merges them into our result - * set based on the details requested by the client. - * - * @param cls our `struct ReserveAttestContext *` - * @param h_payto account for which the attribute data is stored - * @param provider_name provider that must be checked - * @param collection_time when was the data collected - * @param expiration_time when does the data expire - * @param enc_attributes_size number of bytes in @a enc_attributes - * @param enc_attributes encrypted attribute data - */ -static void -kyc_process_cb (void *cls, - const struct TALER_NormalizedPaytoHashP *h_payto, - const char *provider_name, - struct GNUNET_TIME_Timestamp collection_time, - struct GNUNET_TIME_Timestamp expiration_time, - size_t enc_attributes_size, - const void *enc_attributes) -{ - struct ReserveAttestContext *rsc = cls; - json_t *attrs; - json_t *val; - const char *name; - bool match = false; - - if (GNUNET_TIME_absolute_is_past (expiration_time.abs_time)) - return; - attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key, - enc_attributes, - enc_attributes_size); - if (NULL == attrs) - { - GNUNET_break (0); - return; - } - json_object_foreach (attrs, name, val) - { - bool requested = strcmp (name, - "FORM_ID"); /* we always return the FORM_ID */ - size_t idx; - json_t *str; - - if (NULL != json_object_get (rsc->json_attest, - name)) - continue; /* duplicate */ - json_array_foreach (rsc->details, idx, str) - { - if (0 == strcmp (json_string_value (str), - name)) - { - requested = true; - break; - } - } - if (! requested) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Skipping attribute `%s': not requested\n", - name); - continue; - } - match = true; - GNUNET_assert (0 == - json_object_set (rsc->json_attest, /* NOT set_new! */ - name, - val)); - } - json_decref (attrs); - if (! match) - return; - rsc->etime = GNUNET_TIME_timestamp_min (expiration_time, - rsc->etime); -} - - -/** - * Function implementing /reserves/$RID/attest transaction. Given the public - * key of a reserve, return the associated transaction attest. Runs the - * transaction logic; IF it returns a non-error code, the transaction logic - * MUST NOT queue a MHD response. IF it returns an hard error, the - * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it - * returns the soft error code, the function MAY be called again to retry and - * MUST not queue a MHD response. - * - * @param cls a `struct ReserveAttestContext *` - * @param connection MHD request which triggered the transaction - * @param[out] mhd_ret set to MHD response status for @a connection, - * if transaction failed (!); unused - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -reserve_attest_transaction (void *cls, - struct MHD_Connection *connection, - MHD_RESULT *mhd_ret) -{ - struct ReserveAttestContext *rsc = cls; - enum GNUNET_DB_QueryStatus qs; - - rsc->json_attest = json_object (); - GNUNET_assert (NULL != rsc->json_attest); - qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls, - &rsc->h_payto, - &kyc_process_cb, - rsc); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - GNUNET_break (0); - *mhd_ret - = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "select_kyc_attributes"); - return qs; - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - return qs; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - rsc->not_found = true; - return qs; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - rsc->not_found = false; - break; - } - return qs; -} - - -MHD_RESULT -TEH_handler_reserves_attest (struct TEH_RequestContext *rc, - const json_t *root, - const char *const args[1]) -{ - struct ReserveAttestContext rsc = { - .etime = GNUNET_TIME_UNIT_FOREVER_TS - }; - MHD_RESULT mhd_ret; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_timestamp ("request_timestamp", - &rsc.timestamp), - GNUNET_JSON_spec_array_const ("details", - &rsc.details), - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &rsc.reserve_sig), - GNUNET_JSON_spec_end () - }; - struct GNUNET_TIME_Timestamp now; - - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (args[0], - strlen (args[0]), - &rsc.reserve_pub, - sizeof (rsc.reserve_pub))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_RESERVE_PUB_MALFORMED, - args[0]); - } - { - enum GNUNET_GenericReturnValue res; - - res = TALER_MHD_parse_json_data (rc->connection, - root, - spec); - if (GNUNET_SYSERR == res) - { - GNUNET_break (0); - return MHD_NO; /* hard failure */ - } - if (GNUNET_NO == res) - { - GNUNET_break_op (0); - return MHD_YES; /* failure */ - } - } - now = GNUNET_TIME_timestamp_get (); - if (! GNUNET_TIME_absolute_approx_eq (now.abs_time, - rsc.timestamp.abs_time, - TIMESTAMP_TOLERANCE)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW, - NULL); - } - - if (GNUNET_OK != - TALER_wallet_reserve_attest_request_verify (rsc.timestamp, - rsc.details, - &rsc.reserve_pub, - &rsc.reserve_sig)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_RESERVES_ATTEST_BAD_SIGNATURE, - NULL); - } - - { - struct TALER_NormalizedPayto payto_uri; - - payto_uri = TALER_reserve_make_payto (TEH_base_url, - &rsc.reserve_pub); - TALER_normalized_payto_hash (payto_uri, - &rsc.h_payto); - GNUNET_free (payto_uri.normalized_payto); - } - - if (GNUNET_OK != - TEH_DB_run_transaction (rc->connection, - "post reserve attest", - TEH_MT_REQUEST_OTHER, - &mhd_ret, - &reserve_attest_transaction, - &rsc)) - { - return mhd_ret; - } - if (rsc.not_found) - { - json_decref (rsc.json_attest); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, - args[0]); - } - return reply_reserve_attest_success (rc->connection, - &rsc); -} - - -/* end of taler-exchange-httpd_reserves_attest-RESERVE_PUB.c */ diff --git a/src/exchange/taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.h b/src/exchange/taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.h @@ -1,41 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.h - * @brief Handle /reserves/$RESERVE_PUB/attest requests - * @author Christian Grothoff - */ -#ifndef TALER_EXCHANGE_HTTPD_POST_RESERVES_ATTEST_RESERVE_PUB_H -#define TALER_EXCHANGE_HTTPD_POST_RESERVES_ATTEST_RESERVE_PUB_H - -#include <microhttpd.h> -#include "taler-exchange-httpd.h" - - -/** - * Handle a POST "/reserves-attest/$RID" request. - * - * @param rc request context - * @param root uploaded body from the client - * @param args args[0] has public key of the reserve - * @return MHD result code - */ -MHD_RESULT -TEH_handler_reserves_attest (struct TEH_RequestContext *rc, - const json_t *root, - const char *const args[1]); - -#endif diff --git a/src/exchange/taler-exchange-httpd_post-reveal-melt.c b/src/exchange/taler-exchange-httpd_post-reveal-melt.c @@ -123,30 +123,37 @@ find_original_refresh ( case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return GNUNET_OK; /* Only happy case */ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - *result = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN, - NULL); + *result = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN, + NULL); return GNUNET_SYSERR; case GNUNET_DB_STATUS_HARD_ERROR: - *result = TALER_MHD_reply_with_ec (connection, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_refresh"); + *result = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_refresh"); return GNUNET_SYSERR; case GNUNET_DB_STATUS_SOFT_ERROR: break; /* try again */ default: GNUNET_break (0); - *result = TALER_MHD_reply_with_ec (connection, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL); + *result = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); return GNUNET_SYSERR; } } /* after unsuccessful retries*/ - *result = TALER_MHD_reply_with_ec (connection, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_refresh"); + *result = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_refresh"); return GNUNET_SYSERR; } @@ -170,9 +177,11 @@ compare_age_commitment ( if (actx->no_age_commitment != actx->refresh.coin.no_age_commitment) { - *mhd_ret = TALER_MHD_reply_with_ec (connection, - TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID, - NULL); + *mhd_ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID, + NULL); return GNUNET_SYSERR; } if (! actx->no_age_commitment) @@ -188,9 +197,11 @@ compare_age_commitment ( &ach)) { GNUNET_break_op (0); - *mhd_ret = TALER_MHD_reply_with_ec (connection, - TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID, - NULL); + *mhd_ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID, + NULL); return GNUNET_SYSERR; } } @@ -285,11 +296,11 @@ calculate_blinded_detail ( if (GNUNET_OK != ret) { GNUNET_break (0); - *result = TALER_MHD_REPLY_JSON_PACK (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - GNUNET_JSON_pack_string ( - "details", - "failed to prepare planchet from base key")); + *result = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "failed to prepare planchet from base key"); return ret; } } @@ -362,10 +373,11 @@ verify_commitment ( { if (NULL == signatures) { - *result = TALER_MHD_reply_with_error (con, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "signatures missing"); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "signatures missing"); return GNUNET_SYSERR; } } @@ -373,10 +385,11 @@ verify_commitment ( { if (NULL == rev_batch_seeds) { - *result = TALER_MHD_reply_with_error (con, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "batch_seeds missing"); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "batch_seeds missing"); return GNUNET_SYSERR; } } @@ -386,9 +399,11 @@ verify_commitment ( keys = TEH_keys_get_state (); if (NULL == keys) { - *result = TALER_MHD_reply_with_ec (con, - TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, - NULL); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_SERVICE_UNAVAILABLE, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + NULL); return GNUNET_SYSERR; } @@ -405,9 +420,11 @@ verify_commitment ( if (NULL == denom_keys[i]) { GNUNET_break_op (0); - *result = TALER_MHD_reply_with_ec (con, - TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, - NULL); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_SERVICE_UNAVAILABLE, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + NULL); return GNUNET_SYSERR; } @@ -437,9 +454,11 @@ verify_commitment ( if (cs_count != rf->num_cs_r_values) { GNUNET_break (0); - *result = TALER_MHD_reply_with_ec (con, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); return GNUNET_SYSERR; } /** @@ -477,9 +496,11 @@ verify_commitment ( &(*signatures)[sig_idx++])) { GNUNET_break_op (0); - *result = TALER_MHD_reply_with_ec (con, - TALER_EC_EXCHANGE_REFRESHES_REVEAL_LINK_SIGNATURE_INVALID, - NULL); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_REFRESHES_REVEAL_LINK_SIGNATURE_INVALID, + NULL); return GNUNET_SYSERR; } } @@ -642,8 +663,8 @@ verify_commitment ( TALER_refresh_get_commitment (&rc, &rf->refresh_seed, rf->no_blinding_seed - ? NULL - : &rf->blinding_seed, + ? NULL + : &rf->blinding_seed, &kappa_transfer_pubs, &kappa_planchets_h, &rf->coin.coin_pub, @@ -655,9 +676,11 @@ verify_commitment ( &rc.session_hash)) { GNUNET_break_op (0); - *result = TALER_MHD_reply_with_ec (con, - TALER_EC_EXCHANGE_REFRESHES_REVEAL_INVALID_RCH, - "rc"); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_REFRESHES_REVEAL_INVALID_RCH, + "rc"); return GNUNET_SYSERR; } } @@ -692,30 +715,37 @@ commit_reveal ( case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return GNUNET_OK; /* Only happy case */ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - *result = TALER_MHD_reply_with_error (con, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN, - NULL); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN, + NULL); return GNUNET_SYSERR; case GNUNET_DB_STATUS_HARD_ERROR: - *result = TALER_MHD_reply_with_ec (con, - TALER_EC_GENERIC_DB_STORE_FAILED, - "mark_refresh_reveal_success"); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "mark_refresh_reveal_success"); return GNUNET_SYSERR; case GNUNET_DB_STATUS_SOFT_ERROR: break; /* try again */ default: GNUNET_break (0); - *result = TALER_MHD_reply_with_ec (con, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); return GNUNET_SYSERR; } } /* after unsuccessful retries*/ - *result = TALER_MHD_reply_with_ec (con, - TALER_EC_GENERIC_DB_STORE_FAILED, - "mark_refresh_reveal_success"); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "mark_refresh_reveal_success"); return GNUNET_SYSERR; } diff --git a/src/exchange/taler-exchange-httpd_post-reveal-withdraw.c b/src/exchange/taler-exchange-httpd_post-reveal-withdraw.c @@ -119,10 +119,11 @@ parse_withdraw_reveal_json ( NULL)) ) { GNUNET_break_op (0); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - error); + *mhd_ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + error); return GNUNET_SYSERR; } @@ -161,34 +162,38 @@ find_original_withdraw ( case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: return GNUNET_OK; /* Only happy case */ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - *result = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_WITHDRAW_COMMITMENT_UNKNOWN, - NULL); + *result = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_WITHDRAW_COMMITMENT_UNKNOWN, + NULL); return GNUNET_SYSERR; case GNUNET_DB_STATUS_HARD_ERROR: - *result = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_withdraw"); + *result = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_withdraw"); return GNUNET_SYSERR; case GNUNET_DB_STATUS_SOFT_ERROR: break; /* try again */ default: GNUNET_break (0); - *result = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL); + *result = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); return GNUNET_SYSERR; } } /* after unsuccessful retries*/ GNUNET_break (0); - *result = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_withdraw"); + *result = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_withdraw"); return GNUNET_SYSERR; } @@ -270,11 +275,11 @@ calculate_blinded_hash ( if (GNUNET_OK != ret) { GNUNET_break (0); - *result = TALER_MHD_REPLY_JSON_PACK (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - GNUNET_JSON_pack_string ( - "details", - "failed to prepare planchet from base key")); + *result = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "failed to prepare planchet from base key"); return ret; } @@ -351,10 +356,11 @@ verify_commitment_and_max_age ( keys = TEH_keys_get_state (); if (NULL == keys) { - *result = TALER_MHD_reply_with_error (con, - MHD_HTTP_SERVICE_UNAVAILABLE, - TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, - NULL); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_SERVICE_UNAVAILABLE, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + NULL); return GNUNET_SYSERR; } @@ -368,10 +374,11 @@ verify_commitment_and_max_age ( if (NULL == denom_keys[i]) { GNUNET_break_op (0); - *result = TALER_MHD_reply_with_error (con, - MHD_HTTP_SERVICE_UNAVAILABLE, - TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, - NULL); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_SERVICE_UNAVAILABLE, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + NULL); return GNUNET_SYSERR; } diff --git a/src/exchange/taler-exchange-httpd_post-withdraw.c b/src/exchange/taler-exchange-httpd_post-withdraw.c @@ -1337,8 +1337,9 @@ phase_generate_reply_error ( return; case WITHDRAW_ERROR_RESERVE_UNKNOWN: finish_loop (wc, - TALER_MHD_reply_with_ec ( + TALER_MHD_reply_with_error ( wc->rc->connection, + MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, NULL)); return; @@ -1384,36 +1385,41 @@ phase_generate_reply_error ( case WITHDRAW_ERROR_DENOMINATION_REVOKED: GNUNET_break_op (0); finish_loop (wc, - TALER_MHD_reply_with_ec ( + TALER_MHD_reply_with_error ( wc->rc->connection, + MHD_HTTP_GONE, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, NULL)); return; case WITHDRAW_ERROR_CIPHER_MISMATCH: finish_loop (wc, - TALER_MHD_reply_with_ec ( + TALER_MHD_reply_with_error ( wc->rc->connection, + MHD_HTTP_BAD_REQUEST, TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, NULL)); return; case WITHDRAW_ERROR_BLINDING_SEED_REQUIRED: finish_loop (wc, - TALER_MHD_reply_with_ec ( + TALER_MHD_reply_with_error ( wc->rc->connection, + MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MISSING, "blinding_seed")); return; case WITHDRAW_ERROR_CRYPTO_HELPER: finish_loop (wc, - TALER_MHD_reply_with_ec ( + TALER_MHD_reply_with_error ( wc->rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, NULL)); return; case WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN: finish_loop (wc, - TALER_MHD_reply_with_ec ( + TALER_MHD_reply_with_error ( wc->rc->connection, + MHD_HTTP_BAD_REQUEST, TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, "cipher")); return; @@ -1426,8 +1432,9 @@ phase_generate_reply_error ( "denomination %s does not support age restriction", GNUNET_h2s (&wc->error.details.denom_h->hash)); finish_loop (wc, - TALER_MHD_reply_with_ec ( + TALER_MHD_reply_with_error ( wc->rc->connection, + MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN, msg)); return; @@ -1513,8 +1520,9 @@ phase_generate_reply_error ( return; case WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID: finish_loop (wc, - TALER_MHD_reply_with_ec ( + TALER_MHD_reply_with_error ( wc->rc->connection, + MHD_HTTP_FORBIDDEN, TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID, NULL)); return; diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am @@ -36,8 +36,8 @@ libtalerexchange_la_SOURCES = \ exchange_api_get-keys.c \ exchange_api_get-management-keys.c \ exchange_api_get-purses-PURSE_PUB-merge.c \ - exchange_api_get-reserves-attest-RESERVE_PUB.c \ exchange_api_get-reserves-RESERVE_PUB.c \ + exchange_api_get-reserves-RESERVE_PUB-attest.c \ exchange_api_get-reserves-RESERVE_PUB-history.c \ exchange_api_get-transfers-WTID.c \ exchange_api_common.c exchange_api_common.h \ @@ -69,7 +69,7 @@ libtalerexchange_la_SOURCES = \ exchange_api_post-purses-PURSE_PUB-merge.c \ exchange_api_post-recoup-refresh.c \ exchange_api_post-recoup-withdraw.c \ - exchange_api_post-reserves-attest-RESERVE_PUB.c \ + exchange_api_post-reserves-RESERVE_PUB-attest.c \ exchange_api_post-reserves-RESERVE_PUB-close.c \ exchange_api_post-reserves-RESERVE_PUB-open.c \ exchange_api_post-reserves-RESERVE_PUB-purse.c \ diff --git a/src/lib/exchange_api_get-reserves-RESERVE_PUB-attest.c b/src/lib/exchange_api_get-reserves-RESERVE_PUB-attest.c @@ -0,0 +1,296 @@ +/* + This file is part of TALER + Copyright (C) 2014-2026 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-reserves-RESERVE_PUB-attest.c + * @brief Implementation of the GET /reserves/$RESERVE_PUB/attest request + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A GET /reserves/$RESERVE_PUB/attest Handle + */ +struct TALER_EXCHANGE_GetReservesAttestHandle +{ + + /** + * Base URL of the exchange. + */ + char *base_url; + + /** + * The url for this request. + */ + char *url; + + /** + * CURL context to use. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_GetReservesAttestCallback cb; + + /** + * Closure for @e cb. + */ + TALER_EXCHANGE_GET_RESERVES_ATTEST_RESULT_CLOSURE *cb_cls; + + /** + * Public key of the reserve we are querying. + */ + struct TALER_ReservePublicKeyP reserve_pub; + +}; + + +/** + * We received an #MHD_HTTP_OK status code. Handle the JSON response. + * + * @param grah handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_reserves_get_attestable_ok ( + struct TALER_EXCHANGE_GetReservesAttestHandle *grah, + const json_t *j) +{ + struct TALER_EXCHANGE_GetReservesAttestResponse rs = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_OK + }; + const json_t *details; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("details", + &details), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + { + size_t dlen = json_array_size (details); + const char *attributes[GNUNET_NZL (dlen)]; + + for (unsigned int i = 0; i < dlen; i++) + { + json_t *detail = json_array_get (details, + i); + attributes[i] = json_string_value (detail); + if (NULL == attributes[i]) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + rs.details.ok.attributes_length = dlen; + rs.details.ok.attributes = attributes; + grah->cb (grah->cb_cls, + &rs); + grah->cb = NULL; + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP GET /reserves/$RESERVE_PUB/attest request. + * + * @param cls the `struct TALER_EXCHANGE_GetReservesAttestHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserves_get_attestable_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_GetReservesAttestHandle *grah = cls; + const json_t *j = response; + struct TALER_EXCHANGE_GetReservesAttestResponse rs = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + grah->job = NULL; + switch (response_code) + { + case 0: + rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + handle_reserves_get_attestable_ok (grah, + j)) + { + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_CONFLICT: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for reserves get_attestable\n", + (unsigned int) response_code, + (int) rs.hr.ec); + break; + } + if (NULL != grah->cb) + { + grah->cb (grah->cb_cls, + &rs); + grah->cb = NULL; + } + TALER_EXCHANGE_get_reserves_attest_cancel (grah); +} + + +struct TALER_EXCHANGE_GetReservesAttestHandle * +TALER_EXCHANGE_get_reserves_attest_create ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_ReservePublicKeyP *reserve_pub) +{ + struct TALER_EXCHANGE_GetReservesAttestHandle *grah; + + grah = GNUNET_new (struct TALER_EXCHANGE_GetReservesAttestHandle); + grah->ctx = ctx; + grah->base_url = GNUNET_strdup (url); + grah->reserve_pub = *reserve_pub; + return grah; +} + + +enum TALER_ErrorCode +TALER_EXCHANGE_get_reserves_attest_start ( + struct TALER_EXCHANGE_GetReservesAttestHandle *grah, + TALER_EXCHANGE_GetReservesAttestCallback cb, + TALER_EXCHANGE_GET_RESERVES_ATTEST_RESULT_CLOSURE *cb_cls) +{ + char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; + CURL *eh; + + if (NULL != grah->job) + { + GNUNET_break (0); + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + grah->cb = cb; + grah->cb_cls = cb_cls; + { + char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &grah->reserve_pub, + sizeof (grah->reserve_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "reserves/%s/attest", + pub_str); + } + grah->url = TALER_url_join (grah->base_url, + arg_str, + NULL); + if (NULL == grah->url) + return TALER_EC_GENERIC_CONFIGURATION_INVALID; + eh = TALER_EXCHANGE_curl_easy_get_ (grah->url); + if (NULL == eh) + return TALER_EC_GENERIC_CONFIGURATION_INVALID; + grah->job = GNUNET_CURL_job_add (grah->ctx, + eh, + &handle_reserves_get_attestable_finished, + grah); + if (NULL == grah->job) + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + return TALER_EC_NONE; +} + + +void +TALER_EXCHANGE_get_reserves_attest_cancel ( + struct TALER_EXCHANGE_GetReservesAttestHandle *grah) +{ + if (NULL != grah->job) + { + GNUNET_CURL_job_cancel (grah->job); + grah->job = NULL; + } + GNUNET_free (grah->url); + GNUNET_free (grah->base_url); + GNUNET_free (grah); +} + + +/* end of exchange_api_get-reserves-RESERVE_PUB-attest.c */ diff --git a/src/lib/exchange_api_get-reserves-attest-RESERVE_PUB.c b/src/lib/exchange_api_get-reserves-attest-RESERVE_PUB.c @@ -1,296 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2026 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_get-reserves-attest-RESERVE_PUB.c - * @brief Implementation of the GET /reserves/$RESERVE_PUB/attest request - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A GET /reserves/$RESERVE_PUB/attest Handle - */ -struct TALER_EXCHANGE_GetReservesAttestHandle -{ - - /** - * Base URL of the exchange. - */ - char *base_url; - - /** - * The url for this request. - */ - char *url; - - /** - * CURL context to use. - */ - struct GNUNET_CURL_Context *ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_GetReservesAttestCallback cb; - - /** - * Closure for @e cb. - */ - TALER_EXCHANGE_GET_RESERVES_ATTEST_RESULT_CLOSURE *cb_cls; - - /** - * Public key of the reserve we are querying. - */ - struct TALER_ReservePublicKeyP reserve_pub; - -}; - - -/** - * We received an #MHD_HTTP_OK status code. Handle the JSON response. - * - * @param grah handle of the request - * @param j JSON response - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -handle_reserves_get_attestable_ok ( - struct TALER_EXCHANGE_GetReservesAttestHandle *grah, - const json_t *j) -{ - struct TALER_EXCHANGE_GetReservesAttestResponse rs = { - .hr.reply = j, - .hr.http_status = MHD_HTTP_OK - }; - const json_t *details; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_array_const ("details", - &details), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - { - size_t dlen = json_array_size (details); - const char *attributes[GNUNET_NZL (dlen)]; - - for (unsigned int i = 0; i < dlen; i++) - { - json_t *detail = json_array_get (details, - i); - attributes[i] = json_string_value (detail); - if (NULL == attributes[i]) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - rs.details.ok.attributes_length = dlen; - rs.details.ok.attributes = attributes; - grah->cb (grah->cb_cls, - &rs); - grah->cb = NULL; - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP GET /reserves/$RESERVE_PUB/attest request. - * - * @param cls the `struct TALER_EXCHANGE_GetReservesAttestHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserves_get_attestable_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_GetReservesAttestHandle *grah = cls; - const json_t *j = response; - struct TALER_EXCHANGE_GetReservesAttestResponse rs = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - grah->job = NULL; - switch (response_code) - { - case 0: - rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - handle_reserves_get_attestable_ok (grah, - j)) - { - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_CONFLICT: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for reserves get_attestable\n", - (unsigned int) response_code, - (int) rs.hr.ec); - break; - } - if (NULL != grah->cb) - { - grah->cb (grah->cb_cls, - &rs); - grah->cb = NULL; - } - TALER_EXCHANGE_get_reserves_attest_cancel (grah); -} - - -struct TALER_EXCHANGE_GetReservesAttestHandle * -TALER_EXCHANGE_get_reserves_attest_create ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_ReservePublicKeyP *reserve_pub) -{ - struct TALER_EXCHANGE_GetReservesAttestHandle *grah; - - grah = GNUNET_new (struct TALER_EXCHANGE_GetReservesAttestHandle); - grah->ctx = ctx; - grah->base_url = GNUNET_strdup (url); - grah->reserve_pub = *reserve_pub; - return grah; -} - - -enum TALER_ErrorCode -TALER_EXCHANGE_get_reserves_attest_start ( - struct TALER_EXCHANGE_GetReservesAttestHandle *grah, - TALER_EXCHANGE_GetReservesAttestCallback cb, - TALER_EXCHANGE_GET_RESERVES_ATTEST_RESULT_CLOSURE *cb_cls) -{ - char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; - CURL *eh; - - if (NULL != grah->job) - { - GNUNET_break (0); - return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; - } - grah->cb = cb; - grah->cb_cls = cb_cls; - { - char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &grah->reserve_pub, - sizeof (grah->reserve_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "reserves-attest/%s", - pub_str); - } - grah->url = TALER_url_join (grah->base_url, - arg_str, - NULL); - if (NULL == grah->url) - return TALER_EC_GENERIC_CONFIGURATION_INVALID; - eh = TALER_EXCHANGE_curl_easy_get_ (grah->url); - if (NULL == eh) - return TALER_EC_GENERIC_CONFIGURATION_INVALID; - grah->job = GNUNET_CURL_job_add (grah->ctx, - eh, - &handle_reserves_get_attestable_finished, - grah); - if (NULL == grah->job) - return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; - return TALER_EC_NONE; -} - - -void -TALER_EXCHANGE_get_reserves_attest_cancel ( - struct TALER_EXCHANGE_GetReservesAttestHandle *grah) -{ - if (NULL != grah->job) - { - GNUNET_CURL_job_cancel (grah->job); - grah->job = NULL; - } - GNUNET_free (grah->url); - GNUNET_free (grah->base_url); - GNUNET_free (grah); -} - - -/* end of exchange_api_get-reserves-attest-RESERVE_PUB.c */ diff --git a/src/lib/exchange_api_post-reserves-RESERVE_PUB-attest.c b/src/lib/exchange_api_post-reserves-RESERVE_PUB-attest.c @@ -0,0 +1,393 @@ +/* + This file is part of TALER + Copyright (C) 2014-2026 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-reserves-RESERVE_PUB-attest.c + * @brief Implementation of the POST /reserves/$RESERVE_PUB/attest requests + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP attest codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A POST /reserves/$RID/attest Handle + */ +struct TALER_EXCHANGE_PostReservesAttestHandle +{ + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Base URL of the exchange. + */ + char *base_url; + + /** + * The url for this request, set during _start. + */ + char *url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_PostReservesAttestCallback cb; + + /** + * Closure for @a cb. + */ + TALER_EXCHANGE_POST_RESERVES_ATTEST_RESULT_CLOSURE *cb_cls; + + /** + * The keys of the exchange this request handle will use. + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * Public key of the reserve we are querying. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Pre-built JSON body for the request. + */ + json_t *body; + +}; + + +/** + * We received an #MHD_HTTP_OK attest code. Handle the JSON + * response. + * + * @param prah handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_reserves_attest_ok (struct TALER_EXCHANGE_PostReservesAttestHandle *prah, + const json_t *j) +{ + struct TALER_EXCHANGE_PostReservesAttestResponse rs = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_OK + }; + const json_t *attributes; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ("exchange_timestamp", + &rs.details.ok.exchange_time), + GNUNET_JSON_spec_timestamp ("expiration_time", + &rs.details.ok.expiration_time), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rs.details.ok.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rs.details.ok.exchange_pub), + GNUNET_JSON_spec_object_const ("attributes", + &attributes), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (prah->keys, + &rs.details.ok.exchange_pub)) + { + GNUNET_break_op (0); + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE; + prah->cb (prah->cb_cls, + &rs); + prah->cb = NULL; + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + rs.details.ok.attributes = attributes; + if (GNUNET_OK != + TALER_exchange_online_reserve_attest_details_verify ( + rs.details.ok.exchange_time, + rs.details.ok.expiration_time, + &prah->reserve_pub, + attributes, + &rs.details.ok.exchange_pub, + &rs.details.ok.exchange_sig)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + prah->cb (prah->cb_cls, + &rs); + prah->cb = NULL; + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserves/$RID/attest request. + * + * @param cls the `struct TALER_EXCHANGE_PostReservesAttestHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserves_attest_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_PostReservesAttestHandle *prah = cls; + const json_t *j = response; + struct TALER_EXCHANGE_PostReservesAttestResponse rs = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + prah->job = NULL; + switch (response_code) + { + case 0: + rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + handle_reserves_attest_ok (prah, + j)) + { + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_CONFLICT: + /* Server doesn't have the requested attributes */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for reserves attest\n", + (unsigned int) response_code, + (int) rs.hr.ec); + break; + } + if (NULL != prah->cb) + { + prah->cb (prah->cb_cls, + &rs); + prah->cb = NULL; + } + TALER_EXCHANGE_post_reserves_attest_cancel (prah); +} + + +struct TALER_EXCHANGE_PostReservesAttestHandle * +TALER_EXCHANGE_post_reserves_attest_create ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ReservePrivateKeyP *reserve_priv, + unsigned int attributes_length, + const char *attributes[const static attributes_length]) +{ + struct TALER_EXCHANGE_PostReservesAttestHandle *prah; + struct TALER_ReserveSignatureP reserve_sig; + json_t *details; + struct GNUNET_TIME_Timestamp ts; + + if (0 == attributes_length) + { + GNUNET_break (0); + return NULL; + } + details = json_array (); + GNUNET_assert (NULL != details); + for (unsigned int i = 0; i < attributes_length; i++) + { + GNUNET_assert (0 == + json_array_append_new (details, + json_string (attributes[i]))); + } + prah = GNUNET_new (struct TALER_EXCHANGE_PostReservesAttestHandle); + prah->ctx = ctx; + prah->base_url = GNUNET_strdup (url); + prah->keys = TALER_EXCHANGE_keys_incref (keys); + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &prah->reserve_pub.eddsa_pub); + ts = GNUNET_TIME_timestamp_get (); + TALER_wallet_reserve_attest_request_sign (ts, + details, + reserve_priv, + &reserve_sig); + prah->body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("reserve_sig", + &reserve_sig), + GNUNET_JSON_pack_timestamp ("request_timestamp", + ts), + GNUNET_JSON_pack_array_steal ("details", + details)); + if (NULL == prah->body) + { + GNUNET_break (0); + GNUNET_free (prah->base_url); + TALER_EXCHANGE_keys_decref (prah->keys); + GNUNET_free (prah); + return NULL; + } + return prah; +} + + +enum TALER_ErrorCode +TALER_EXCHANGE_post_reserves_attest_start ( + struct TALER_EXCHANGE_PostReservesAttestHandle *prah, + TALER_EXCHANGE_PostReservesAttestCallback cb, + TALER_EXCHANGE_POST_RESERVES_ATTEST_RESULT_CLOSURE *cb_cls) +{ + CURL *eh; + char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; + + prah->cb = cb; + prah->cb_cls = cb_cls; + { + char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &prah->reserve_pub, + sizeof (prah->reserve_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "reserves/%s/attest", + pub_str); + } + prah->url = TALER_url_join (prah->base_url, + arg_str, + NULL); + if (NULL == prah->url) + return TALER_EC_GENERIC_CONFIGURATION_INVALID; + eh = TALER_EXCHANGE_curl_easy_get_ (prah->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&prah->post_ctx, + eh, + prah->body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + GNUNET_free (prah->url); + prah->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + prah->job = GNUNET_CURL_job_add2 (prah->ctx, + eh, + prah->post_ctx.headers, + &handle_reserves_attest_finished, + prah); + if (NULL == prah->job) + { + TALER_curl_easy_post_finished (&prah->post_ctx); + GNUNET_free (prah->url); + prah->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + return TALER_EC_NONE; +} + + +void +TALER_EXCHANGE_post_reserves_attest_cancel ( + struct TALER_EXCHANGE_PostReservesAttestHandle *prah) +{ + if (NULL != prah->job) + { + GNUNET_CURL_job_cancel (prah->job); + prah->job = NULL; + } + TALER_curl_easy_post_finished (&prah->post_ctx); + json_decref (prah->body); + GNUNET_free (prah->url); + GNUNET_free (prah->base_url); + TALER_EXCHANGE_keys_decref (prah->keys); + GNUNET_free (prah); +} + + +/* end of exchange_api_post-reserves-RESERVE_PUB-attest.c */ diff --git a/src/lib/exchange_api_post-reserves-attest-RESERVE_PUB.c b/src/lib/exchange_api_post-reserves-attest-RESERVE_PUB.c @@ -1,393 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2026 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_post-reserves-attest-RESERVE_PUB.c - * @brief Implementation of the POST /reserves-attest/$RESERVE_PUB requests - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP attest codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A POST /reserves-attest/$RID Handle - */ -struct TALER_EXCHANGE_PostReservesAttestHandle -{ - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; - - /** - * Base URL of the exchange. - */ - char *base_url; - - /** - * The url for this request, set during _start. - */ - char *url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_PostReservesAttestCallback cb; - - /** - * Closure for @a cb. - */ - TALER_EXCHANGE_POST_RESERVES_ATTEST_RESULT_CLOSURE *cb_cls; - - /** - * The keys of the exchange this request handle will use. - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * Public key of the reserve we are querying. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Pre-built JSON body for the request. - */ - json_t *body; - -}; - - -/** - * We received an #MHD_HTTP_OK attest code. Handle the JSON - * response. - * - * @param prah handle of the request - * @param j JSON response - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -handle_reserves_attest_ok (struct TALER_EXCHANGE_PostReservesAttestHandle *prah, - const json_t *j) -{ - struct TALER_EXCHANGE_PostReservesAttestResponse rs = { - .hr.reply = j, - .hr.http_status = MHD_HTTP_OK - }; - const json_t *attributes; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_timestamp ("exchange_timestamp", - &rs.details.ok.exchange_time), - GNUNET_JSON_spec_timestamp ("expiration_time", - &rs.details.ok.expiration_time), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rs.details.ok.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rs.details.ok.exchange_pub), - GNUNET_JSON_spec_object_const ("attributes", - &attributes), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (prah->keys, - &rs.details.ok.exchange_pub)) - { - GNUNET_break_op (0); - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE; - prah->cb (prah->cb_cls, - &rs); - prah->cb = NULL; - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - rs.details.ok.attributes = attributes; - if (GNUNET_OK != - TALER_exchange_online_reserve_attest_details_verify ( - rs.details.ok.exchange_time, - rs.details.ok.expiration_time, - &prah->reserve_pub, - attributes, - &rs.details.ok.exchange_pub, - &rs.details.ok.exchange_sig)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - prah->cb (prah->cb_cls, - &rs); - prah->cb = NULL; - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reserves-attest/$RID request. - * - * @param cls the `struct TALER_EXCHANGE_PostReservesAttestHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserves_attest_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_PostReservesAttestHandle *prah = cls; - const json_t *j = response; - struct TALER_EXCHANGE_PostReservesAttestResponse rs = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - prah->job = NULL; - switch (response_code) - { - case 0: - rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - handle_reserves_attest_ok (prah, - j)) - { - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - GNUNET_break (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - GNUNET_break (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_CONFLICT: - /* Server doesn't have the requested attributes */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for reserves attest\n", - (unsigned int) response_code, - (int) rs.hr.ec); - break; - } - if (NULL != prah->cb) - { - prah->cb (prah->cb_cls, - &rs); - prah->cb = NULL; - } - TALER_EXCHANGE_post_reserves_attest_cancel (prah); -} - - -struct TALER_EXCHANGE_PostReservesAttestHandle * -TALER_EXCHANGE_post_reserves_attest_create ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_ReservePrivateKeyP *reserve_priv, - unsigned int attributes_length, - const char *attributes[const static attributes_length]) -{ - struct TALER_EXCHANGE_PostReservesAttestHandle *prah; - struct TALER_ReserveSignatureP reserve_sig; - json_t *details; - struct GNUNET_TIME_Timestamp ts; - - if (0 == attributes_length) - { - GNUNET_break (0); - return NULL; - } - details = json_array (); - GNUNET_assert (NULL != details); - for (unsigned int i = 0; i < attributes_length; i++) - { - GNUNET_assert (0 == - json_array_append_new (details, - json_string (attributes[i]))); - } - prah = GNUNET_new (struct TALER_EXCHANGE_PostReservesAttestHandle); - prah->ctx = ctx; - prah->base_url = GNUNET_strdup (url); - prah->keys = TALER_EXCHANGE_keys_incref (keys); - GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, - &prah->reserve_pub.eddsa_pub); - ts = GNUNET_TIME_timestamp_get (); - TALER_wallet_reserve_attest_request_sign (ts, - details, - reserve_priv, - &reserve_sig); - prah->body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("reserve_sig", - &reserve_sig), - GNUNET_JSON_pack_timestamp ("request_timestamp", - ts), - GNUNET_JSON_pack_array_steal ("details", - details)); - if (NULL == prah->body) - { - GNUNET_break (0); - GNUNET_free (prah->base_url); - TALER_EXCHANGE_keys_decref (prah->keys); - GNUNET_free (prah); - return NULL; - } - return prah; -} - - -enum TALER_ErrorCode -TALER_EXCHANGE_post_reserves_attest_start ( - struct TALER_EXCHANGE_PostReservesAttestHandle *prah, - TALER_EXCHANGE_PostReservesAttestCallback cb, - TALER_EXCHANGE_POST_RESERVES_ATTEST_RESULT_CLOSURE *cb_cls) -{ - CURL *eh; - char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; - - prah->cb = cb; - prah->cb_cls = cb_cls; - { - char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &prah->reserve_pub, - sizeof (prah->reserve_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "reserves-attest/%s", - pub_str); - } - prah->url = TALER_url_join (prah->base_url, - arg_str, - NULL); - if (NULL == prah->url) - return TALER_EC_GENERIC_CONFIGURATION_INVALID; - eh = TALER_EXCHANGE_curl_easy_get_ (prah->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&prah->post_ctx, - eh, - prah->body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - GNUNET_free (prah->url); - prah->url = NULL; - return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; - } - prah->job = GNUNET_CURL_job_add2 (prah->ctx, - eh, - prah->post_ctx.headers, - &handle_reserves_attest_finished, - prah); - if (NULL == prah->job) - { - TALER_curl_easy_post_finished (&prah->post_ctx); - GNUNET_free (prah->url); - prah->url = NULL; - return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; - } - return TALER_EC_NONE; -} - - -void -TALER_EXCHANGE_post_reserves_attest_cancel ( - struct TALER_EXCHANGE_PostReservesAttestHandle *prah) -{ - if (NULL != prah->job) - { - GNUNET_CURL_job_cancel (prah->job); - prah->job = NULL; - } - TALER_curl_easy_post_finished (&prah->post_ctx); - json_decref (prah->body); - GNUNET_free (prah->url); - GNUNET_free (prah->base_url); - TALER_EXCHANGE_keys_decref (prah->keys); - GNUNET_free (prah); -} - - -/* end of exchange_api_post-reserves-attest-RESERVE_PUB.c */ diff --git a/src/lib/exchange_api_post-withdraw.c b/src/lib/exchange_api_post-withdraw.c @@ -387,7 +387,6 @@ copy_results ( case MHD_HTTP_CREATED: resp.details.created = wbr->details.created; break; - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: resp.details.unavailable_for_legal_reasons = wbr->details.unavailable_for_legal_reasons; @@ -435,12 +434,6 @@ copy_results_with_age_proof ( /* in the age-restricted case, this should not happen */ GNUNET_break_op (0); break; - - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - resp.details.unavailable_for_legal_reasons = - wbr->details.unavailable_for_legal_reasons; - break; - case MHD_HTTP_CREATED: { GNUNET_assert (wh->num_coins == wbr->details.created.num_coins); @@ -457,7 +450,10 @@ copy_results_with_age_proof ( } break; } - + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + resp.details.unavailable_for_legal_reasons = + wbr->details.unavailable_for_legal_reasons; + break; default: break; } diff --git a/src/lib/exchange_api_post-withdraw_blinded.c b/src/lib/exchange_api_post-withdraw_blinded.c @@ -432,6 +432,14 @@ handle_withdraw_blinded_finished ( wbr.hr.ec = TALER_JSON_get_error_code (j_response); wbr.hr.hint = TALER_JSON_get_error_hint (j_response); break; + case MHD_HTTP_PRECONDITION_FAILED: + /* could happen if we were too early and the denomination + is not yet available */ + /* Note: one might want to check the "Date" header to + see if our clock is very far off */ + wbr.hr.ec = TALER_JSON_get_error_code (j_response); + wbr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: /* only validate reply is well-formed */ { @@ -456,6 +464,7 @@ handle_withdraw_blinded_finished ( }; wbr.hr.ec = TALER_JSON_get_error_code (j_response); + wbr.hr.hint = TALER_JSON_get_error_hint (j_response); if (GNUNET_OK != GNUNET_JSON_parse (j_response, spec, @@ -474,6 +483,23 @@ handle_withdraw_blinded_finished ( wbr.hr.ec = TALER_JSON_get_error_code (j_response); wbr.hr.hint = TALER_JSON_get_error_hint (j_response); break; + case MHD_HTTP_NOT_IMPLEMENTED: + /* Server does not implement a feature (usually the cipher) */ + wbr.hr.ec = TALER_JSON_get_error_code (j_response); + wbr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_BAD_GATEWAY: + /* Server could not talk to another component, usually this + indicates a problem with the secmod helper */ + wbr.hr.ec = TALER_JSON_get_error_code (j_response); + wbr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_SERVICE_UNAVAILABLE: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + wbr.hr.ec = TALER_JSON_get_error_code (j_response); + wbr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; default: /* unexpected response code */ GNUNET_break_op (0);