donau

Donation authority for GNU Taler (experimental)
Log | Files | Refs | Submodules | README | LICENSE

commit 31c610a7d4cf814b425b4f8e2605c0378a9bc386
parent 76fa63265e0b90a7e10120a7605d8a749b9e6230
Author: Matyja Lukas Adam <lukas.matyja@students.bfh.ch>
Date:   Sun,  7 Jan 2024 18:07:40 +0100

merge and add some json helpers

Diffstat:
Mdoc/.gitignore | 1+
Mpo/POTFILES.in | 2+-
Msrc/donau/Makefile.am | 7+++++--
Msrc/donau/donau-httpd.c | 64++++++++++++++++++++++++++--------------------------------------
Asrc/donau/donau-httpd_charity.h | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/donau/donau-httpd_db.c | 95+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Asrc/donau/donau-httpd_get-charities.c | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/donau/donau-httpd_get-charity.c | 22++++++++++------------
Msrc/donau/donau-httpd_keys.c | 740+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/donau/donau-httpd_keys.h | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Asrc/donau/donau-httpd_metrics.c | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/donau/donau-httpd_metrics.h | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/donau/donau-httpd_post-charity.c | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/donaudb/0002-donation_units.sql | 2+-
Msrc/donaudb/0002-donau_charities.sql | 3++-
Msrc/donaudb/Makefile.am | 25++++++++++++++++++++++++-
Msrc/donaudb/pg_activate_signing_key.c | 2+-
Msrc/donaudb/pg_activate_signing_key.h | 2+-
Msrc/donaudb/pg_add_donation_unit_key.c | 9+++++----
Msrc/donaudb/pg_add_donation_unit_key.h | 3++-
Asrc/donaudb/pg_get_charities.c | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/donaudb/pg_get_charities.h | 39+++++++++++++++++++++++++++++++++++++++
Dsrc/donaudb/pg_get_donation_unit_info.c | 67-------------------------------------------------------------------
Dsrc/donaudb/pg_get_donation_unit_info.h | 41-----------------------------------------
Msrc/donaudb/pg_helper.h | 65+++++++++++++++++++++++++++++++++++------------------------------
Msrc/donaudb/pg_insert_charity.c | 11++++++++---
Msrc/donaudb/pg_insert_charity.h | 4+++-
Msrc/donaudb/pg_insert_history_entry.c | 3++-
Msrc/donaudb/pg_insert_issued_receipt.c | 6++++--
Msrc/donaudb/pg_insert_issued_receipt.h | 3+--
Msrc/donaudb/pg_insert_submitted_receipt.c | 7++++---
Msrc/donaudb/pg_insert_submitted_receipt.h | 7++++---
Msrc/donaudb/pg_lookup_charity.c | 9+++++----
Msrc/donaudb/pg_lookup_charity.h | 4++--
Asrc/donaudb/pg_lookup_donation_unit.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/donaudb/pg_lookup_donation_unit_key.h -> src/donaudb/pg_lookup_donation_unit.h | 0
Dsrc/donaudb/pg_lookup_donation_unit_key.c | 61-------------------------------------------------------------
Msrc/donaudb/pg_lookup_signing_key.c | 2+-
Msrc/donaudb/pg_lookup_signing_key.h | 2+-
Msrc/include/donau_crypto_lib.h | 14++++++++++++++
Asrc/include/donau_json_lib.h | 44++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/donau_service.h | 26+++++++++++++++++++-------
Msrc/include/donaudb_plugin.h | 114++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/json/Makefile.am | 5++++-
Asrc/json/donau_json.c | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/json/json.c | 763++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Asrc/json/json_helper.c | 1621+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/json/json_pack.c | 324+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/Makefile.am | 4++--
Msrc/lib/donau_api_handle.c | 6+++---
Msrc/util/donau_signatures.c | 2+-
51 files changed, 4820 insertions(+), 492 deletions(-)

diff --git a/doc/.gitignore b/doc/.gitignore @@ -1,3 +1,4 @@ +*.bak *.aux *.dvi *.log diff --git a/po/POTFILES.in b/po/POTFILES.in @@ -1,2 +1,2 @@ # List of source files which contain translatable strings. -src/util/taler_error_codes.c +#src/util/taler_error_codes.c diff --git a/src/donau/Makefile.am b/src/donau/Makefile.am @@ -27,7 +27,6 @@ donau_httpd_LDADD = \ -lmicrohttpd \ -ltalermhd \ -ltalerutil \ - -ltalertemplating \ -lgnunetcurl \ -lgnunetutil \ -lgnunetjson \ @@ -38,8 +37,12 @@ donau_httpd_LDADD = \ donau_httpd_SOURCES = \ donau-httpd.c donau-httpd.h \ + donau-httpd_metrics.c donau-httpd_metrics.h \ + donau-httpd_db.c donau-httpd_db.h \ donau-httpd_keys.c donau-httpd_keys.h \ - donau-httpd_config.c donau-httpd_config.h + donau-httpd_config.c donau-httpd_config.h \ + donau-httpd_get-charities.c donau_httpd_charity.h \ + donau-httpd_get-charity.c donau-httpd_post-charity.c # Testcases diff --git a/src/donau/donau-httpd.c b/src/donau/donau-httpd.c @@ -35,6 +35,7 @@ #include "donau_util.h" #include "donau-httpd_config.h" #include "donau-httpd_keys.h" +#include "donau-httpd_charity.h" /** * Backlog for listen operation on unix domain sockets. @@ -176,15 +177,15 @@ typedef MHD_RESULT * @param connection where to send the reply on * @param details details for the error message, can be NULL */ -static MHD_RESULT -r404 (struct MHD_Connection *connection, - const char *details) -{ - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN, - details); -} +// static MHD_RESULT +// r404 (struct MHD_Connection *connection, +// const char *details) +// { +// return TALER_MHD_reply_with_error (connection, +// MHD_HTTP_NOT_FOUND, +// TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN, +// details); +// } /** @@ -387,31 +388,6 @@ proceed_with_handler (struct DH_RequestContext *rc, /** - * Handle a GET "/management" request. - * - * @param rc request context - * @param args array of additional options (must be [0] == "keys") - * @return MHD result code - */ -static MHD_RESULT -handle_get_management (struct DH_RequestContext *rc, - const char *const args[2]) -{ - if ( (NULL != args[0]) && - (0 == strcmp (args[0], - "keys")) && - (NULL == args[1]) ) - { - return DH_keys_management_get_keys_handler (rc->rh, - rc->connection); - } - GNUNET_break_op (0); - return r404 (rc->connection, - "/management/*"); -} - - -/** * Handle incoming HTTP request. * * @param cls closure for MHD daemon (unused) @@ -450,13 +426,25 @@ handle_mhd_request (void *cls, .method = MHD_HTTP_METHOD_GET, .handler.get = &DH_handler_config }, - /* GET management endpoints (we only really have "/management/keys") */ + /* GET keys endpoints (we only really have "/keys") */ { - .url = "management", + .url = "keys", .method = MHD_HTTP_METHOD_GET, - .handler.get = &handle_get_management, - .nargs = 1 + .handler.get = &DH_keys_get_handler// , + // .nargs = 1 }, + /* GET charities */ + { + .url = "charities", + .method = MHD_HTTP_METHOD_GET, + .handler.get = &DH_handler_charities_get + }, + /** + etc + + Add routes here (also make sure to add the + corresponding lines in the makefile) + */ /* mark end of list */ { .url = NULL diff --git a/src/donau/donau-httpd_charity.h b/src/donau/donau-httpd_charity.h @@ -0,0 +1,66 @@ +/* + This file is part of TALER + Copyright (C) 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 donau-httpd_charity.h + * @brief Handle /charity requests + * @author Johannes Casaburi + */ +#ifndef DONAU_HTTPD_CHARITY_H +#define DONAU_HTTPD_CHARITY_H + +#include <microhttpd.h> +#include "donau-httpd.h" + + +/** + * Handle a POST "/charity" request. + * + * @param connection the MHD connection to handle + * @param root uploaded JSON data + * @return MHD result code + */ +MHD_RESULT +TEH_handler_charity_post ( + struct MHD_Connection *connection, + const json_t *root); + + +/** + * Handle a GET "/charity" request. + * + * @param rc request context + * @param args GET arguments (should be one) + * @return MHD result code + */ +MHD_RESULT +DH_handler_charity_get ( + struct DH_RequestContext *rc, + const char *const args[]); + + +/** + * Handle a GET "/charities" request. + * + * @param rc request context + * @param args GET arguments (should be one) + * @return MHD result code + */ +MHD_RESULT +DH_handler_charities_get ( + struct DH_RequestContext *rc, + const char *const args[]); + +#endif diff --git a/src/donau/donau-httpd_db.c b/src/donau/donau-httpd_db.c @@ -26,7 +26,7 @@ #include "taler/taler_mhd_lib.h" #include "donaudb_lib.h" #include "donau-httpd_db.h" -#include "donau-httpd_responses.h" +// #include "donau-httpd_responses.h" enum GNUNET_GenericReturnValue @@ -39,17 +39,17 @@ DH_DB_run_transaction (struct MHD_Connection *connection, { if (NULL != mhd_ret) *mhd_ret = -1; /* set to invalid value, to help detect bugs */ - // if (GNUNET_OK != - // DH_plugin->preflight (DH_plugin->cls)) - // { - // GNUNET_break (0); - // if (NULL != mhd_ret) - // *mhd_ret = TALER_MHD_reply_with_error (connection, - // MHD_HTTP_INTERNAL_SERVER_ERROR, - // TALER_EC_GENERIC_DB_SETUP_FAILED, - // NULL); - // return GNUNET_SYSERR; - // } + if (GNUNET_OK != + DH_plugin->preflight (DH_plugin->cls)) + { + GNUNET_break (0); + if (NULL != mhd_ret) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_SETUP_FAILED, + NULL); + return GNUNET_SYSERR; + } GNUNET_assert (mt < DH_MT_REQUEST_COUNT); DH_METRICS_num_requests[mt]++; for (unsigned int retries = 0; @@ -58,43 +58,46 @@ DH_DB_run_transaction (struct MHD_Connection *connection, { enum GNUNET_DB_QueryStatus qs; - // if (GNUNET_OK != - // DH_plugin->start (DH_plugin->cls, - // name)) - // { - // GNUNET_break (0); - // if (NULL != mhd_ret) - // *mhd_ret = TALER_MHD_reply_with_error (connection, - // MHD_HTTP_INTERNAL_SERVER_ERROR, - // TALER_EC_GENERIC_DB_START_FAILED, - // NULL); - // return GNUNET_SYSERR; - // } + if (GNUNET_OK != + DH_plugin->start (DH_plugin->cls, + name)) + { + GNUNET_break (0); + if (NULL != mhd_ret) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_START_FAILED, + NULL); + return GNUNET_SYSERR; + } qs = cb (cb_cls, connection, mhd_ret); - // if (0 > qs) - // { - // DH_plugin->rollback (DH_plugin->cls); - // if (GNUNET_DB_STATUS_HARD_ERROR == qs) - // return GNUNET_SYSERR; - // } - // else - // { - // qs = DH_plugin->commit (DH_plugin->cls); - // if (GNUNET_DB_STATUS_HARD_ERROR == qs) - // { - // DH_plugin->rollback (DH_plugin->cls); - // if (NULL != mhd_ret) - // *mhd_ret = TALER_MHD_reply_with_error (connection, - // MHD_HTTP_INTERNAL_SERVER_ERROR, - // TALER_EC_GENERIC_DB_COMMIT_FAILED, - // NULL); - // return GNUNET_SYSERR; - // } - // if (0 > qs) - // DH_plugin->rollback (DH_plugin->cls); - // } + if (0 > qs) + { + DH_plugin->rollback (DH_plugin->cls); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + return GNUNET_SYSERR; + } + else + { + qs = DH_plugin->commit (DH_plugin->cls); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + DH_plugin->rollback (DH_plugin->cls); + if (NULL != mhd_ret) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + NULL); + return GNUNET_SYSERR; + } + if (0 > qs) + DH_plugin->rollback (DH_plugin->cls); + } + /* make sure callback did not violate invariants! */ + GNUNET_assert ( (NULL == mhd_ret) || + (-1 == (int) *mhd_ret) ); if (0 <= qs) return GNUNET_OK; DH_METRICS_num_conflict[mt]++; diff --git a/src/donau/donau-httpd_get-charities.c b/src/donau/donau-httpd_get-charities.c @@ -0,0 +1,119 @@ +/* + This file is part of TALER + Copyright (C) 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 donau-httpd_get-charities.c + * @brief Return charities + * @author Johannes Casaburi + */ +#include "taler/platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <pthread.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_mhd_lib.h" +#include "taler/taler_signatures.h" +#include "donau-httpd.h" +#include "donaudb_plugin.h" +#include "donau-httpd_charity.h" +// #include "taler-exchange-httpd_metrics.h" + + +/** + * Maximum number of charities we return per request. + */ +#define MAX_RECORDS 1024 + +/** + * Return charities information. + * + * @param cls closure + */ +static void +charities_cb ( + void *cls, + const char *charity_url, + const char *charity_name) +{ + json_t *charities = cls; + + GNUNET_assert ( + 0 == + json_array_append ( + charities, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("url", + charity_url), + GNUNET_JSON_pack_string ("name", + charity_name) + ))); +} + + +MHD_RESULT +DH_handler_charities_get ( + struct DH_RequestContext *rc, + const char *const args[]) +{ + + if (NULL != args[1]) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_ENDPOINT_UNKNOWN, + args[1]); + } + + { + json_t *charities; + enum GNUNET_DB_QueryStatus qs; + + charities = json_array (); + GNUNET_assert (NULL != charities); + qs = DH_plugin->get_charities (DH_plugin->cls, + &charities_cb, + charities); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + case GNUNET_DB_STATUS_SOFT_ERROR: + json_decref (charities); + GNUNET_break (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + NULL); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + return TALER_MHD_reply_static ( + rc->connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + return TALER_MHD_REPLY_JSON_PACK ( + rc->connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_array_steal ("charities", + charities)); + } +} + + +/* end of donau-httpd_get-charities.c */ diff --git a/src/donau/donau-httpd_get-charity.c b/src/donau/donau-httpd_get-charity.c @@ -18,7 +18,7 @@ * @brief Return summary information about AML decision * @author Johannes Casaburi */ -#include "platform.h" +#include "taler/platform.h" #include <gnunet/gnunet_util_lib.h> #include <jansson.h> #include <microhttpd.h> @@ -26,10 +26,10 @@ #include "taler/taler_json_lib.h" #include "taler/taler_mhd_lib.h" #include "taler/taler_signatures.h" -#include "donau-httpd.h" #include "donaudb_plugin.h" -#include "donau-httpd_aml-decision.h" -#include "donau-httpd_metrics.h" +#include "donau-httpd_charity.h" +// #include "donau-httpd.h" +// #include "donau-httpd_metrics.h" /** @@ -70,11 +70,10 @@ DH_handler_charity_get ( bool none = false; MHD_RESULT result; - GNUNET_assert (NULL != charity_info); - qs = DH_plugin->select_charity_info (DH_plugin->cls, - charity_id, - &charity_url, - &charity_name); + qs = DH_plugin->get_charity (DH_plugin->cls, + charity_id, + &charity_url, + &charity_name); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: @@ -100,9 +99,8 @@ DH_handler_charity_get ( } result = TALER_MHD_REPLY_JSON_PACK ( - connection, - http_status, - TALER_MHD_PACK_EC (ec), + rc->connection, + MHD_HTTP_OK, GNUNET_JSON_pack_string ("url", charity_url), GNUNET_JSON_pack_string ("name", diff --git a/src/donau/donau-httpd_keys.c b/src/donau/donau-httpd_keys.c @@ -24,11 +24,9 @@ #include "taler/taler_mhd_lib.h" #include "donau-httpd.h" #include "donau-httpd_keys.h" -// #include "donau-httpd_config.h" -// #include "donau-httpd_responses.h" +#include "donau-httpd_config.h" #include "donaudb_plugin.h" - /** * How many /keys request do we hold in suspension at * most at any time? @@ -36,6 +34,224 @@ #define SKR_LIMIT 32 /** + * When do we forcefully timeout a /keys request? + */ +#define KEYS_TIMEOUT GNUNET_TIME_UNIT_MINUTES + +/** + * Number of entries in the @e skr_head DLL. + */ +static unsigned int skr_size; + +/** + * Handle to a connection that should be force-resumed + * with a hard error due to @a skr_size hitting + * #SKR_LIMIT. + */ +static struct MHD_Connection *skr_connection; + +/** + * Entry of /keys requests that are currently suspended because we are + * waiting for /keys to become ready. + */ +struct SuspendedKeysRequests +{ + /** + * Kept in a DLL. + */ + struct SuspendedKeysRequests *next; + + /** + * Kept in a DLL. + */ + struct SuspendedKeysRequests *prev; + + /** + * The suspended connection. + */ + struct MHD_Connection *connection; + + /** + * When does this request timeout? + */ + struct GNUNET_TIME_Absolute timeout; +}; + +/** + * Head of DLL of suspended /keys requests. + */ +static struct SuspendedKeysRequests *skr_head; + +/** + * Tail of DLL of suspended /keys requests. + */ +static struct SuspendedKeysRequests *skr_tail; + +/** + * Task to force timeouts on /keys requests. + */ +static struct GNUNET_SCHEDULER_Task *keys_tt; + +/** + * Are we shutting down? + */ +static bool terminating; + +/** + * Function called to forcefully resume suspended keys requests. + * + * @param cls unused, NULL + */ +static void +keys_timeout_cb (void *cls) +{ + struct SuspendedKeysRequests *skr; + + (void) cls; + keys_tt = NULL; + while (NULL != (skr = skr_head)) + { + if (GNUNET_TIME_absolute_is_future (skr->timeout)) + break; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Resuming /keys request due to timeout\n"); + GNUNET_CONTAINER_DLL_remove (skr_head, + skr_tail, + skr); + MHD_resume_connection (skr->connection); + TALER_MHD_daemon_trigger (); + GNUNET_free (skr); + } + if (NULL == skr) + return; + keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout, + &keys_timeout_cb, + NULL); +} + + +/** + * Suspend /keys request while we (hopefully) are waiting to be + * provisioned with key material. + * + * @param[in] connection to suspend + */ +static MHD_RESULT +suspend_request (struct MHD_Connection *connection) +{ + struct SuspendedKeysRequests *skr; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Suspending /keys request until key material changes\n"); + if (terminating) + { + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + "Exchange terminating"); + } + skr = GNUNET_new (struct SuspendedKeysRequests); + skr->connection = connection; + MHD_suspend_connection (connection); + GNUNET_CONTAINER_DLL_insert (skr_head, + skr_tail, + skr); + skr->timeout = GNUNET_TIME_relative_to_absolute (KEYS_TIMEOUT); + if (NULL == keys_tt) + { + keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout, + &keys_timeout_cb, + NULL); + } + skr_size++; + if (skr_size > SKR_LIMIT) + { + skr = skr_tail; + GNUNET_CONTAINER_DLL_remove (skr_head, + skr_tail, + skr); + skr_size--; + skr_connection = skr->connection; + MHD_resume_connection (skr->connection); + TALER_MHD_daemon_trigger (); + GNUNET_free (skr); + } + return MHD_YES; +} + + +/** + * Information about a donation unit on offer by the donation unit helper. + */ +struct HelperDonationUnit +{ + + /** + * When will the helper start to use this key for signing? + */ + struct GNUNET_TIME_Timestamp start_time; + + /** + * For how long will the helper allow signing? 0 if + * the key was revoked or purged. + */ + struct GNUNET_TIME_Relative validity_duration; + + /** + * Hash of the full donation unit key. + */ + struct DONAU_DonationUnitHashP h_donation_unit_pub; + + /** + * The (full) public key. + */ + struct DONAU_DonationUnitPublicKey donation_unit_pub; + + /** + * Details depend on the @e donation_unit_pub.cipher type. + */ + union + { + + /** + * Hash of the RSA key. + */ + struct TALER_RsaPubHashP h_rsa; + + /** + * Hash of the CS key. + */ + struct TALER_CsPubHashP h_cs; + + } h_details; + + /** + * Name in configuration section for this donation unit type. + */ + char *section_name; + + +}; + +/** + * Information about a signing key on offer by the sign helper. + */ +struct HelperSignkey +{ + /** + * When will the helper start to use this key for signing? + */ + // struct GNUNET_TIME_Timestamp start_time; + int year; + + /** + * The public key. + */ + struct DONAU_DonauPublicKeyP donau_pub; + +}; + +/** * Counter incremented whenever we have a reason to re-build the keys because * something external changed. See #DH_keys_get_state() and * #DH_keys_update_states() for uses of this variable. @@ -45,17 +261,17 @@ static uint64_t key_generation; /** * RSA security module public key, all zero if not known. */ -static struct TALER_SecurityModulePublicKeyP donation_unit_rsa_sm_pub; +// static struct TALER_SecurityModulePublicKeyP donation_unit_rsa_sm_pub; /** * CS security module public key, all zero if not known. */ -static struct TALER_SecurityModulePublicKeyP donation_unit_cs_sm_pub; +// static struct TALER_SecurityModulePublicKeyP donation_unit_cs_sm_pub; /** * EdDSA security module public key, all zero if not known. */ -static struct TALER_SecurityModulePublicKeyP esign_sm_pub; +// static struct TALER_SecurityModulePublicKeyP esign_sm_pub; /** @@ -64,13 +280,6 @@ static struct TALER_SecurityModulePublicKeyP esign_sm_pub; #define KEYS_TIMEOUT GNUNET_TIME_UNIT_MINUTES /** - * Obtain the key state if we should NOT run finish_keys_response() because we - * only need the state for the /management/keys API - */ -struct DH_KeyStateHandle * -DH_keys_get_state_for_management_only (void); - -/** * Stores the latest generation of our key state. */ static struct DH_KeyStateHandle *key_state; @@ -98,13 +307,13 @@ struct DH_KeyStateHandle { /** - * Mapping from denomination keys to denomination key issue struct. + * Mapping from donation unit keys to donation unit key issue struct. * Used to lookup the key by hash. */ struct GNUNET_CONTAINER_MultiHashMap *denomkey_map; /** - * Map from `struct TALER_ExchangePublicKey` to `struct SigningKey` + * Map from `struct DONAU_DonauPublicKey` to `struct SigningKey` * entries. Based on the fact that a `struct GNUNET_PeerIdentity` is also * an EdDSA public key. */ @@ -239,23 +448,18 @@ struct HelperState }; /** - * Closure for #add_denom_key_cb. + * Closure for #add_donation_unit_key_cb. */ -struct DenomKeyCtx +struct DonationUnitKeyCtx { /** - * Heap for sorting active denomination keys by start time. + * Heap for sorting active donation unit keys by start time. */ struct GNUNET_CONTAINER_Heap *heap; /** - * JSON array of revoked denomination keys. - */ - json_t *recoup; - - /** * What is the minimum key rotation frequency of - * valid denomination keys? + * valid donation unit keys? */ struct GNUNET_TIME_Relative min_dk_frequency; }; @@ -373,7 +577,7 @@ finish_keys_response (struct DH_KeyStateHandle *ksh) enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; json_t *recoup; struct SignKeyCtx sctx; - json_t *grouped_denominations = NULL; + json_t *grouped_donation_units = NULL; struct GNUNET_TIME_Timestamp last_cherry_pick_date; struct GNUNET_CONTAINER_Heap *heap; struct GNUNET_HashContext *hash_context = NULL; @@ -389,11 +593,10 @@ finish_keys_response (struct DH_KeyStateHandle *ksh) GNUNET_assert (NULL != recoup); heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MAX); { - struct DenomKeyCtx dkc = { - .recoup = recoup, - .heap = heap, - .min_dk_frequency = GNUNET_TIME_UNIT_FOREVER_REL, - }; + // struct DonationUnitKeyCtx dkc = { + // .heap = heap, + // .min_dk_frequency = GNUNET_TIME_UNIT_FOREVER_REL, + // }; // GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map, // &add_denom_key_cb, @@ -405,8 +608,8 @@ finish_keys_response (struct DH_KeyStateHandle *ksh) hash_context = GNUNET_CRYPTO_hash_context_start (); - grouped_denominations = json_array (); - GNUNET_assert (NULL != grouped_denominations); + grouped_donation_units = json_array (); + GNUNET_assert (NULL != grouped_donation_units); last_cherry_pick_date = GNUNET_TIME_UNIT_ZERO_TS; @@ -436,14 +639,14 @@ finish_keys_response (struct DH_KeyStateHandle *ksh) else { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "No denomination keys available. Refusing to generate /keys response.\n"); + "No donation unit keys available. Refusing to generate /keys response.\n"); GNUNET_CRYPTO_hash_context_abort (hash_context); } ret = GNUNET_OK; -CLEANUP: - json_decref (grouped_denominations); +// CLEANUP: + json_decref (grouped_donation_units); json_decref (sctx.signkeys); json_decref (recoup); return ret; @@ -451,17 +654,17 @@ CLEANUP: /** - * Free denomination key data. + * Free donation unit key data. * * @param cls a `struct DH_KeyStateHandle`, unused - * @param h_donation_unit_pub hash of the denomination public key, unused + * @param h_donation_unit_pub hash of the donation unit public key, unused * @param value a `struct DH_DonationUnitKey` to free * @return #GNUNET_OK (continue to iterate) */ static enum GNUNET_GenericReturnValue -clear_denomination_cb (void *cls, - const struct GNUNET_HashCode *h_donation_unit_pub, - void *value) +clear_donation_unit_cb (void *cls, + const struct GNUNET_HashCode *h_donation_unit_pub, + void *value) { struct DH_DonationUnitKey *dk = value; @@ -474,7 +677,7 @@ clear_denomination_cb (void *cls, /** - * Free denomination key data. + * Free donation unit key data. * * @param cls a `struct DH_KeyStateHandle`, unused * @param pid the online signing key (type-disguised), unused @@ -594,7 +797,7 @@ destroy_key_state (struct DH_KeyStateHandle *ksh, // GNUNET_free (gf); // } GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map, - &clear_denomination_cb, + &clear_donation_unit_cb, ksh); GNUNET_CONTAINER_multihashmap_destroy (ksh->denomkey_map); GNUNET_CONTAINER_multipeermap_iterate (ksh->signkey_map, @@ -677,11 +880,11 @@ setup_key_helpers (struct HelperState *hs) * @return NULL on error (i.e. failed to access database) */ static struct DH_KeyStateHandle * -build_key_state (struct HelperState *hs, - bool management_only) +build_key_state (struct HelperState *hs /*, + bool management_only*/) { struct DH_KeyStateHandle *ksh; - enum GNUNET_DB_QueryStatus qs; + // enum GNUNET_DB_QueryStatus qs; ksh = GNUNET_new (struct DH_KeyStateHandle); ksh->signature_expires = GNUNET_TIME_UNIT_FOREVER_TS; @@ -743,11 +946,11 @@ build_key_state (struct HelperState *hs, // return NULL; // } - if (management_only) - { - ksh->management_only = true; - return ksh; - } + // if (management_only) + // { + // ksh->management_only = true; + // return ksh; + // } if (GNUNET_OK != finish_keys_response (ksh)) @@ -781,14 +984,14 @@ DH_keys_update_states () static struct DH_KeyStateHandle * -keys_get_state (bool management_only) +DH_keys_get_state (/*bool management_only*/) { struct DH_KeyStateHandle *old_ksh; struct DH_KeyStateHandle *ksh; old_ksh = key_state; if (NULL == old_ksh) { - // ksh = build_key_state (NULL, management_only); + ksh = build_key_state (NULL /*, management_only*/); ksh = NULL; if (NULL == ksh) return NULL; @@ -802,8 +1005,8 @@ keys_get_state (bool management_only) "Rebuilding /keys, generation upgrade from %llu to %llu\n", (unsigned long long) old_ksh->key_generation, (unsigned long long) key_generation); - ksh = build_key_state (old_ksh->helpers, - management_only); + ksh = build_key_state (old_ksh->helpers /*, + management_only*/); key_state = ksh; old_ksh->helpers = NULL; destroy_key_state (old_ksh, @@ -815,92 +1018,375 @@ keys_get_state (bool management_only) } -struct DH_KeyStateHandle * -DH_keys_get_state_for_management_only (void) +/** + * Closure for #add_future_donation_unit_cb and #add_future_signkey_cb. + */ +struct FutureBuilderContext +{ + /** + * Our key state. + */ + struct DH_KeyStateHandle *ksh; + + /** + * Array of donation unit keys. + */ + json_t *donation_units; + + /** + * Array of signing keys. + */ + json_t *signkeys; + +}; + +// /** +// * Function called on all of our current and future donation unit keys +// * known to the helper process. Filters out those that are current +// * and adds the remaining donation unit keys (with their configuration +// * data) to the JSON array. +// * +// * @param cls the `struct FutureBuilderContext *` +// * @param h_donation_unit_pub hash of the donation unit public key +// * @param value a `struct HelperDenomination` +// * @return #GNUNET_OK (continue to iterate) +// */ +// static enum GNUNET_GenericReturnValue +// add_donation_unitkey_cb (void *cls, +// const struct GNUNET_HashCode *h_donation_unit_pub, +// void *value) +// { +// struct FutureBuilderContext *fbc = cls; +// struct HelperDonationUnit *helper_donation_unit = value; +// struct DH_DonationUnitKey *donation_unit_key; +// struct DONAUDB_DonationUnitKeyMetaData meta = {0}; + +// donation_unit_key = GNUNET_CONTAINER_multihashmap_get (fbc->ksh->denomkey_map, +// h_donation_unit_pub); +// if (NULL != donation_unit_key) +// return GNUNET_OK; /* skip: this key is already active! */ +// // if (GNUNET_TIME_relative_is_zero (hd->validity_duration)) +// // return GNUNET_OK; /* this key already expired! */ + +// GNUNET_assert ( +// 0 == +// json_array_append_new ( +// fbc->donation_units, +// GNUNET_JSON_PACK ( +// TALER_JSON_pack_amount ("value", +// &meta.value), +// GNUNET_JSON_pack_uint64 ("year", +// meta.validity_year), +// GNUNET_JSON_pack_data_auto ("donation_unit_pub", +// &helper_donation_unit->donation_unit_pub) +// // GNUNET_JSON_pack_string ("section_name", +// // helper_donation_unit->section_name) +// ))); +// return GNUNET_OK; +// } + + +// /** +// * Function called on all of our current and future exchange signing keys +// * known to the helper process. Filters out those that are current +// * and adds the remaining signing keys (with their configuration +// * data) to the JSON array. +// * +// * @param cls the `struct FutureBuilderContext *` +// * @param pid actually the exchange public key (type disguised) +// * @param value a `struct HelperDenomination` +// * @return #GNUNET_OK (continue to iterate) +// */ +// static enum GNUNET_GenericReturnValue +// add_signkey_cb (void *cls, +// const struct GNUNET_PeerIdentity *pid, +// void *value) +// { +// struct FutureBuilderContext *fbc = cls; +// struct HelperSignkey *hsk = value; +// struct SigningKey *sk; +// // struct GNUNET_TIME_Timestamp stamp_expire; +// // struct GNUNET_TIME_Timestamp legal_end; + +// sk = GNUNET_CONTAINER_multipeermap_get (fbc->ksh->signkey_map, +// pid); +// if (NULL != sk) +// return GNUNET_OK; /* skip: this key is already active */ +// // if (GNUNET_TIME_relative_is_zero (hsk->validity_duration)) +// // return GNUNET_OK; /* this key already expired! */ +// // stamp_expire = GNUNET_TIME_absolute_to_timestamp ( +// // GNUNET_TIME_absolute_add (hsk->start_time.abs_time, +// // hsk->validity_duration)); +// // legal_end = GNUNET_TIME_absolute_to_timestamp ( +// // GNUNET_TIME_absolute_add (stamp_expire.abs_time, +// // signkey_legal_duration)); +// GNUNET_assert (0 == +// json_array_append_new ( +// fbc->signkeys, +// GNUNET_JSON_PACK ( +// GNUNET_JSON_pack_data_auto ("key", +// &hsk->donau_pub), +// // GNUNET_JSON_pack_timestamp ("stamp_end", +// // legal_end), +// GNUNET_JSON_pack_data_auto ("year", +// &hsk->year) +// // GNUNET_JSON_pack_data_auto ("signkey_secmod_sig", +// // &hsk->sm_sig) +// ))); +// return GNUNET_OK; +// } + + +// MHD_RESULT +// DH_get_keys_handler (const struct DH_RequestHandler *rh, +// struct MHD_Connection *connection) +// { +// struct DH_KeyStateHandle *ksh; +// json_t *reply; + +// (void) rh; +// ksh = keys_get_state (true); +// if (NULL == ksh) +// { +// return TALER_MHD_reply_with_error (connection, +// MHD_HTTP_SERVICE_UNAVAILABLE, +// TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, +// "no key state"); +// } +// sync_key_helpers (ksh->helpers); +// if (NULL == ksh->management_keys_reply) +// { +// struct FutureBuilderContext fbc = { +// .ksh = ksh, +// .donation_units = json_array (), +// .signkeys = json_array () +// }; +// if ( (GNUNET_is_zero (&donation_unit_rsa_sm_pub)) && +// (GNUNET_is_zero (&donation_unit_cs_sm_pub)) ) +// { +// /* Either IPC failed, or neither helper had any donation_unit configured. */ +// return TALER_MHD_reply_with_error (connection, +// MHD_HTTP_BAD_GATEWAY, +// TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE, +// NULL); +// } +// if (GNUNET_is_zero (&esign_sm_pub)) +// { +// return TALER_MHD_reply_with_error (connection, +// MHD_HTTP_BAD_GATEWAY, +// TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE, +// NULL); +// } +// GNUNET_assert (NULL != fbc.donation_units); +// GNUNET_assert (NULL != fbc.signkeys); +// GNUNET_CONTAINER_multihashmap_iterate (ksh->helpers->donation_unit_keys, +// &add_donation_unitkey_cb, +// &fbc); +// GNUNET_CONTAINER_multipeermap_iterate (ksh->helpers->esign_keys, +// &add_signkey_cb, +// &fbc); +// reply = GNUNET_JSON_PACK ( +// GNUNET_JSON_pack_array_steal ("future_donation_units", +// fbc.donation_units), +// GNUNET_JSON_pack_array_steal ("future_signkeys", +// fbc.signkeys), +// GNUNET_JSON_pack_data_auto ("donation_unit_secmod_public_key", +// &donation_unit_rsa_sm_pub), +// GNUNET_JSON_pack_data_auto ("donation_unit_secmod_cs_public_key", +// &donation_unit_cs_sm_pub), +// GNUNET_JSON_pack_data_auto ("signkey_secmod_public_key", +// &esign_sm_pub)); +// GNUNET_log (GNUNET_ERROR_TYPE_INFO, +// "Returning GET /keys response:\n"); +// if (NULL == reply) +// return TALER_MHD_reply_with_error (connection, +// MHD_HTTP_INTERNAL_SERVER_ERROR, +// TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE, +// NULL); +// GNUNET_assert (NULL == ksh->management_keys_reply); +// ksh->management_keys_reply = reply; +// } +// else +// { +// reply = ksh->management_keys_reply; +// } +// return TALER_MHD_reply_json (connection, +// reply, +// MHD_HTTP_OK); +// } + +/** + * Comparator used for a binary search by cherry_pick_date for @a key in the + * `struct KeysResponseData` array. See libc's qsort() and bsearch() functions. + * + * @param key pointer to a `struct GNUNET_TIME_Timestamp` + * @param value pointer to a `struct KeysResponseData` array entry + * @return 0 if time matches, -1 if key is smaller, 1 if key is larger + */ +static int +krd_search_comparator (const void *key, + const void *value) +{ + const struct GNUNET_TIME_Timestamp *kd = key; + const struct KeysResponseData *krd = value; + + if (GNUNET_TIME_timestamp_cmp (*kd, + >, + krd->cherry_pick_date)) + return -1; + if (GNUNET_TIME_timestamp_cmp (*kd, + <, + krd->cherry_pick_date)) + return 1; + return 0; +} + + +/** + * Callback used to set headers in a response. + * + * @param cls closure + * @param[in,out] resp response to modify + */ +typedef void +(*TEH_RESPONSE_SetHeaders)(void *cls, + struct MHD_Response *resp); + + +MHD_RESULT +DH_RESPONSE_reply_not_modified ( + struct MHD_Connection *connection, + const char *etags, + TEH_RESPONSE_SetHeaders cb, + void *cb_cls) { - return keys_get_state (true); + MHD_RESULT ret; + struct MHD_Response *resp; + + resp = MHD_create_response_from_buffer (0, + NULL, + MHD_RESPMEM_PERSISTENT); + cb (cb_cls, + resp); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_ETAG, + etags)); + ret = MHD_queue_response (connection, + MHD_HTTP_NOT_MODIFIED, + resp); + GNUNET_break (MHD_YES == ret); + MHD_destroy_response (resp); + return ret; } MHD_RESULT -DH_keys_management_get_keys_handler (const struct DH_RequestHandler *rh, - struct MHD_Connection *connection) +DH_keys_get_handler (struct DH_RequestContext *rc, + const char *const args[]) { - struct DH_KeyStateHandle *ksh; - json_t *reply; + struct GNUNET_TIME_Timestamp last_issue_date; + const char *etag; + + etag = MHD_lookup_connection_value (rc->connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_IF_NONE_MATCH); + (void) args; + // { + // const char *have_cherrypick; + + // have_cherrypick = MHD_lookup_connection_value (rc->connection, + // MHD_GET_ARGUMENT_KIND, + // "last_issue_date"); + // if (NULL != have_cherrypick) + // { + // unsigned long long cherrypickn; + + // if (1 != + // sscanf (have_cherrypick, + // "%llu", + // &cherrypickn)) + // { + // GNUNET_break_op (0); + // return TALER_MHD_reply_with_error (rc->connection, + // MHD_HTTP_BAD_REQUEST, + // TALER_EC_GENERIC_PARAMETER_MALFORMED, + // have_cherrypick); + // } + // /* The following multiplication may overflow; but this should not really + // be a problem, as giving back 'older' data than what the client asks for + // (given that the client asks for data in the distant future) is not + // problematic */ + // last_issue_date = GNUNET_TIME_timestamp_from_s (cherrypickn); + // } + // else + // { + // last_issue_date = GNUNET_TIME_UNIT_ZERO_TS; + // } + // } - (void) rh; - // ksh = DH_keys_get_state_for_management_only (); - ksh = NULL; - if (NULL == ksh) - { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_SERVICE_UNAVAILABLE, - TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, - "no key state"); - } - sync_key_helpers (ksh->helpers); - if (NULL == ksh->management_keys_reply) { - // struct FutureBuilderContext fbc = { - // .ksh = ksh, - // .donation_units = json_array (), - // .signkeys = json_array () - // }; - if ( (GNUNET_is_zero (&donation_unit_rsa_sm_pub)) && - (GNUNET_is_zero (&donation_unit_cs_sm_pub)) ) + struct DH_KeyStateHandle *ksh; + const struct KeysResponseData *krd; + + ksh = DH_keys_get_state (); + if ( (NULL == ksh) || + (0 == ksh->krd_array_length) ) { - /* Either IPC failed, or neither helper had any donation_unitinations configured. */ - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_GATEWAY, - TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE, - NULL); + if ( ( (SKR_LIMIT == skr_size) && + (rc->connection == skr_connection) ) || + DH_suicide) + { + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_SERVICE_UNAVAILABLE, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + DH_suicide + ? "server terminating" + : "too many connections suspended waiting on /keys"); + } + return suspend_request (rc->connection); } - if (GNUNET_is_zero (&esign_sm_pub)) + krd = bsearch (&last_issue_date, + ksh->krd_array, + ksh->krd_array_length, + sizeof (struct KeysResponseData), + &krd_search_comparator); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Filtering /keys by cherry pick date %s found entry %u/%u\n", + GNUNET_TIME_timestamp2s (last_issue_date), + (unsigned int) (krd - ksh->krd_array), + ksh->krd_array_length); + if ( (NULL == krd) && + (ksh->krd_array_length > 0) ) { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_GATEWAY, - TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE, - NULL); + if (! GNUNET_TIME_absolute_is_zero (last_issue_date.abs_time)) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Client provided invalid cherry picking timestamp %s, returning full response\n", + GNUNET_TIME_timestamp2s (last_issue_date)); + krd = &ksh->krd_array[ksh->krd_array_length - 1]; } - // GNUNET_assert (NULL != fbc.donation_units); - // GNUNET_assert (NULL != fbc.signkeys); - // GNUNET_CONTAINER_multihashmap_iterate (ksh->helpers->donation_unit_keys, - // &add_future_donation_unitkey_cb, - // &fbc); - // GNUNET_CONTAINER_multipeermap_iterate (ksh->helpers->esign_keys, - // &add_future_signkey_cb, - // &fbc); - reply = GNUNET_JSON_PACK ( - // GNUNET_JSON_pack_array_steal ("future_donation_units", - // fbc.donation_units), - // GNUNET_JSON_pack_array_steal ("future_signkeys", - // fbc.signkeys), - // GNUNET_JSON_pack_data_auto ("master_pub", - // &DH_master_public_key), - GNUNET_JSON_pack_data_auto ("donation_unit_secmod_public_key", - &donation_unit_rsa_sm_pub), - GNUNET_JSON_pack_data_auto ("donation_unit_secmod_cs_public_key", - &donation_unit_cs_sm_pub), - GNUNET_JSON_pack_data_auto ("signkey_secmod_public_key", - &esign_sm_pub)); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning GET /management/keys response:\n"); - if (NULL == reply) - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE, - NULL); - GNUNET_assert (NULL == ksh->management_keys_reply); - ksh->management_keys_reply = reply; - } - else - { - reply = ksh->management_keys_reply; + if (NULL == krd) + { + /* Likely keys not ready *yet*. + Wait until they are. */ + return suspend_request (rc->connection); + } + if ( (NULL != etag) && + (0 == strcmp (etag, + krd->etag)) ) + return DH_RESPONSE_reply_not_modified (rc->connection, + krd->etag, + &setup_general_response_headers, + ksh); + + return MHD_queue_response (rc->connection, + MHD_HTTP_OK, + (MHD_YES == + TALER_MHD_can_compress (rc->connection)) + ? krd->response_compressed + : krd->response_uncompressed); } - return TALER_MHD_reply_json (connection, - reply, - MHD_HTTP_OK); } diff --git a/src/donau/donau-httpd_keys.h b/src/donau/donau-httpd_keys.h @@ -57,16 +57,125 @@ struct DH_DonationUnitKey }; /** - * Function to call to handle requests to "/management/keys" by sending - * back our future key material. + * Information needed to create a blind signature. + */ +// struct DH_CoinSignData +// { +/** + * Hash of key to sign with. + */ +// const struct TALER_DenominationHashP *h_denom_pub; + +/** + * Blinded planchet to sign over. + */ +// const struct TALER_BlindedPlanchet *bp; +// }; + + +// /** +// * Request to sign @a csds. +// * +// * @param csds array with data to blindly sign (and keys to sign with) +// * @param csds_length length of @a csds array +// * @param for_melt true if this is for a melt operation +// * @param[out] bss array set to the blind signature on success; must be of length @a csds_length +// * @return #TALER_EC_NONE on success +// */ +// enum TALER_ErrorCode +// TEH_keys_denomination_batch_sign ( +// unsigned int csds_length, +// const struct TEH_CoinSignData csds[static csds_length], +// bool for_melt, +// struct TALER_BlindedDenominationSignature bss[static csds_length]); + + +// /** +// * Information needed to derive the CS r_pub. +// */ +// struct TEH_CsDeriveData +// { +// /** +// * Hash of key to sign with. +// */ +// const struct TALER_DenominationHashP *h_denom_pub; + +// /** +// * Nonce to use. +// */ +// const struct GNUNET_CRYPTO_CsSessionNonce *nonce; +// }; + + +// /** +// * Request to derive CS @a r_pub using the denomination and nonce from @a cdd. +// * +// * @param cdd data to compute @a r_pub from +// * @param for_melt true if this is for a melt operation +// * @param[out] r_pub where to write the result +// * @return #TALER_EC_NONE on success +// */ +// enum TALER_ErrorCode +// TEH_keys_denomination_cs_r_pub ( +// const struct TEH_CsDeriveData *cdd, +// bool for_melt, +// struct GNUNET_CRYPTO_CSPublicRPairP *r_pub); + +// /** +// * Request to derive a bunch of CS @a r_pubs using the +// * denominations and nonces from @a cdds. +// * +// * @param cdds array to compute @a r_pubs from +// * @param cdds_length length of the @a cdds array +// * @param for_melt true if this is for a melt operation +// * @param[out] r_pubs array where to write the result; must be of length @a cdds_length +// * @return #TALER_EC_NONE on success +// */ +// enum TALER_ErrorCode +// TEH_keys_denomination_cs_batch_r_pub ( +// unsigned int cdds_length, +// const struct TEH_CsDeriveData cdds[static cdds_length], +// bool for_melt, +// struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static cdds_length]); + + +/** + * Fully clean up keys subsystem. + */ +void +TEH_keys_finished (void); + + +/** + * Resumes all suspended /keys requests, we may now have key material + * (or are shutting down). * - * @param rh context of the handler - * @param connection the MHD connection to handle + * @param do_shutdown are we shutting down? + */ +void +TEH_resume_keys_requests (bool do_shutdown); + + +/** + * Function to call to handle requests to "/keys" by sending + * back our current key material. + * + * @param rc request context + * @param args array of additional options (must be empty for this function) * @return MHD result code */ MHD_RESULT -DH_keys_management_get_keys_handler (const struct DH_RequestHandler *rh, - struct MHD_Connection *connection); +DH_keys_get_handler (struct DH_RequestContext *rc, + const char *const args[]); + + +/** + * Initialize keys subsystem. + * + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +TEH_keys_init (void); #endif diff --git a/src/donau/donau-httpd_metrics.c b/src/donau/donau-httpd_metrics.c @@ -0,0 +1,165 @@ +/* + This file is part of TALER + Copyright (C) 2015-2021 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 donau-httpd_metrics.c + * @brief Handle /metrics requests + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <gnunet/gnunet_json_lib.h> +#include "taler/taler_dbevents.h" +// #include "donau-httpd_responses.h" +#include "donau-httpd_keys.h" +#include "donau-httpd_metrics.h" +#include "taler/taler_json_lib.h" +#include "taler/taler_mhd_lib.h" +#include <jansson.h> + + +unsigned long long DH_METRICS_num_requests[DH_MT_REQUEST_COUNT]; + +unsigned long long DH_METRICS_batch_withdraw_num_coins; + +unsigned long long DH_METRICS_num_conflict[DH_MT_REQUEST_COUNT]; + +unsigned long long DH_METRICS_num_signatures[DH_MT_SIGNATURE_COUNT]; + +unsigned long long DH_METRICS_num_verifications[DH_MT_SIGNATURE_COUNT]; + +unsigned long long DH_METRICS_num_keyexchanges[DH_MT_KEYX_COUNT]; + +unsigned long long DH_METRICS_num_success[DH_MT_SUCCESS_COUNT]; + + +MHD_RESULT +DH_handler_metrics (struct DH_RequestContext *rc, + const char *const args[]) +{ + char *reply; + struct MHD_Response *resp; + MHD_RESULT ret; + + (void) args; + GNUNET_asprintf (&reply, + "taler_exchange_success_transactions{type=\"%s\"} %llu\n" + "taler_exchange_success_transactions{type=\"%s\"} %llu\n" + "taler_exchange_success_transactions{type=\"%s\"} %llu\n" + "taler_exchange_success_transactions{type=\"%s\"} %llu\n" + "taler_exchange_success_transactions{type=\"%s\"} %llu\n" + "# HELP taler_exchange_serialization_failures " + " number of database serialization errors by type\n" + "# TYPE taler_exchange_serialization_failures counter\n" + "taler_exchange_serialization_failures{type=\"%s\"} %llu\n" + "taler_exchange_serialization_failures{type=\"%s\"} %llu\n" + "taler_exchange_serialization_failures{type=\"%s\"} %llu\n" + "taler_exchange_serialization_failures{type=\"%s\"} %llu\n" + "# HELP taler_exchange_received_requests " + " number of received requests by type\n" + "# TYPE taler_exchange_received_requests counter\n" + "taler_exchange_received_requests{type=\"%s\"} %llu\n" + "taler_exchange_received_requests{type=\"%s\"} %llu\n" + "taler_exchange_received_requests{type=\"%s\"} %llu\n" + "taler_exchange_received_requests{type=\"%s\"} %llu\n" + "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n" +#if NOT_YET_IMPLEMENTED + "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n" + "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n" +#endif + "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n" + "# HELP taler_exchange_num_signatures " + " number of signatures created by cipher\n" + "# TYPE taler_exchange_num_signatures counter\n" + "taler_exchange_num_signatures{type=\"%s\"} %llu\n" + "taler_exchange_num_signatures{type=\"%s\"} %llu\n" + "taler_exchange_num_signatures{type=\"%s\"} %llu\n" + "# HELP taler_exchange_num_signature_verifications " + " number of signatures verified by cipher\n" + "# TYPE taler_exchange_num_signature_verifications counter\n" + "taler_exchange_num_signature_verifications{type=\"%s\"} %llu\n" + "taler_exchange_num_signature_verifications{type=\"%s\"} %llu\n" + "taler_exchange_num_signature_verifications{type=\"%s\"} %llu\n" + "# HELP taler_exchange_num_keyexchanges " + " number of key exchanges done by cipher\n" + "# TYPE taler_exchange_num_keyexchanges counter\n" + "taler_exchange_num_keyexchanges{type=\"%s\"} %llu\n" + "# HELP taler_exchange_batch_withdraw_num_coins " + " number of coins withdrawn in a batch-withdraw request\n" + "# TYPE taler_exchange_batch_withdraw_num_coins counter\n" + "taler_exchange_batch_withdraw_num_coins{} %llu\n", + "deposit", + DH_METRICS_num_success[DH_MT_SUCCESS_DEPOSIT], + "withdraw", + DH_METRICS_num_success[DH_MT_SUCCESS_WITHDRAW], + "batch-withdraw", + DH_METRICS_num_success[DH_MT_SUCCESS_BATCH_WITHDRAW], + "melt", + DH_METRICS_num_success[DH_MT_SUCCESS_MELT], + "refresh-reveal", + DH_METRICS_num_success[DH_MT_SUCCESS_REFRESH_REVEAL], + "other", + DH_METRICS_num_conflict[DH_MT_REQUEST_OTHER], + "deposit", + DH_METRICS_num_conflict[DH_MT_REQUEST_DEPOSIT], + "withdraw", + DH_METRICS_num_conflict[DH_MT_REQUEST_WITHDRAW], + "melt", + DH_METRICS_num_conflict[DH_MT_REQUEST_MELT], + "other", + DH_METRICS_num_requests[DH_MT_REQUEST_OTHER], + "deposit", + DH_METRICS_num_requests[DH_MT_REQUEST_DEPOSIT], + "withdraw", + DH_METRICS_num_requests[DH_MT_REQUEST_WITHDRAW], + "melt", + DH_METRICS_num_requests[DH_MT_REQUEST_MELT], + "withdraw", + DH_METRICS_num_requests[DH_MT_REQUEST_IDEMPOTENT_WITHDRAW], +#if NOT_YET_IMPLEMENTED + "deposit", + DH_METRICS_num_requests[DH_MT_REQUEST_IDEMPOTENT_DEPOSIT], + "melt", + DH_METRICS_num_requests[DH_MT_REQUEST_IDEMPOTENT_MELT], +#endif + "batch-withdraw", + DH_METRICS_num_requests[ + DH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW], + "rsa", + DH_METRICS_num_signatures[DH_MT_SIGNATURE_RSA], + "cs", + DH_METRICS_num_signatures[DH_MT_SIGNATURE_CS], + "eddsa", + DH_METRICS_num_signatures[DH_MT_SIGNATURE_EDDSA], + "rsa", + DH_METRICS_num_verifications[DH_MT_SIGNATURE_RSA], + "cs", + DH_METRICS_num_verifications[DH_MT_SIGNATURE_CS], + "eddsa", + DH_METRICS_num_verifications[DH_MT_SIGNATURE_EDDSA], + "ecdh", + DH_METRICS_num_keyexchanges[DH_MT_KEYX_ECDH], + DH_METRICS_batch_withdraw_num_coins); + resp = MHD_create_response_from_buffer (strlen (reply), + reply, + MHD_RESPMEM_MUST_FREE); + ret = MHD_queue_response (rc->connection, + MHD_HTTP_OK, + resp); + MHD_destroy_response (resp); + return ret; +} + + +/* end of donau-httpd_metrics.c */ diff --git a/src/donau/donau-httpd_metrics.h b/src/donau/donau-httpd_metrics.h @@ -0,0 +1,136 @@ +/* + This file is part of TALER + Copyright (C) 2014--2021 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 donau-httpd_metrics.h + * @brief Handle /metrics requests + * @author Christian Grothoff + */ +#ifndef DONAU_HTTPD_METRICS_H +#define DONAU_HTTPD_METRICS_H + +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "donau-httpd.h" + + +/** + * Request types for which we collect metrics. + */ +enum DH_MetricTypeRequest +{ + DH_MT_REQUEST_OTHER = 0, + DH_MT_REQUEST_DEPOSIT = 1, + DH_MT_REQUEST_WITHDRAW = 2, + DH_MT_REQUEST_AGE_WITHDRAW = 3, + DH_MT_REQUEST_MELT = 4, + DH_MT_REQUEST_PURSE_CREATE = 5, + DH_MT_REQUEST_PURSE_MERGE = 6, + DH_MT_REQUEST_RESERVE_PURSE = 7, + DH_MT_REQUEST_PURSE_DEPOSIT = 8, + DH_MT_REQUEST_IDEMPOTENT_DEPOSIT = 9, + DH_MT_REQUEST_IDEMPOTENT_WITHDRAW = 10, + DH_MT_REQUEST_IDEMPOTENT_AGE_WITHDRAW = 11, + DH_MT_REQUEST_IDEMPOTENT_MELT = 12, + DH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW = 13, + DH_MT_REQUEST_BATCH_DEPOSIT = 14, + DH_MT_REQUEST_POLICY_FULFILLMENT = 15, + DH_MT_REQUEST_COUNT = 16 /* MUST BE LAST! */ +}; + +/** + * Success types for which we collect metrics. + */ +enum DH_MetricTypeSuccess +{ + DH_MT_SUCCESS_DEPOSIT = 0, + DH_MT_SUCCESS_WITHDRAW = 1, + DH_MT_SUCCESS_AGE_WITHDRAW = 2, + DH_MT_SUCCESS_BATCH_WITHDRAW = 3, + DH_MT_SUCCESS_MELT = 4, + DH_MT_SUCCESS_REFRESH_REVEAL = 5, + DH_MT_SUCCESS_AGE_WITHDRAW_REVEAL = 6, + DH_MT_SUCCESS_COUNT = 7 /* MUST BE LAST! */ +}; + +/** + * Cipher types for which we collect signature metrics. + */ +enum DH_MetricTypeSignature +{ + DH_MT_SIGNATURE_RSA = 0, + DH_MT_SIGNATURE_CS = 1, + DH_MT_SIGNATURE_EDDSA = 2, + DH_MT_SIGNATURE_COUNT = 3 +}; + +/** + * Cipher types for which we collect key exchange metrics. + */ +enum DH_MetricTypeKeyX +{ + DH_MT_KEYX_ECDH = 0, + DH_MT_KEYX_COUNT = 1 +}; + +/** + * Number of requests handled of the respective type. + */ +extern unsigned long long DH_METRICS_num_requests[DH_MT_REQUEST_COUNT]; + +/** + * Number of successful requests handled of the respective type. + */ +extern unsigned long long DH_METRICS_num_success[DH_MT_SUCCESS_COUNT]; + +/** + * Number of coins withdrawn in a batch-withdraw request + */ +extern unsigned long long DH_METRICS_batch_withdraw_num_coins; + +/** + * Number of serialization errors encountered when + * handling requests of the respective type. + */ +extern unsigned long long DH_METRICS_num_conflict[DH_MT_REQUEST_COUNT]; + +/** + * Number of signatures created by the respective cipher. + */ +extern unsigned long long DH_METRICS_num_signatures[DH_MT_SIGNATURE_COUNT]; + +/** + * Number of signatures verified by the respective cipher. + */ +extern unsigned long long DH_METRICS_num_verifications[DH_MT_SIGNATURE_COUNT]; + +/** + * Number of key exchanges done with the respective cipher. + */ +extern unsigned long long DH_METRICS_num_keyexchanges[DH_MT_KEYX_COUNT]; + +/** + * Handle a "/metrics" request. + * + * @param rc request context + * @param args array of additional options (must be empty for this function) + * @return MHD result code + */ +MHD_RESULT +DH_handler_metrics (struct DH_RequestContext *rc, + const char *const args[]); + + +#endif diff --git a/src/donau/donau-httpd_post-charity.c b/src/donau/donau-httpd_post-charity.c @@ -0,0 +1,151 @@ +/* + This file is part of TALER + Copyright (C) 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 donau-httpd_post-charity.c + * @brief Handle request to insert a charity. + * @author Johannes Casaburi + */ +#include "taler/platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <pthread.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_mhd_lib.h" +#include "taler/taler_signatures.h" +#include "donaudb_plugin.h" +#include "donau-httpd_charity.h" +#include "donau-httpd_db.h" +#include "donau-httpd_metrics.h" + + +/** + * Closure for #insert_charity() + */ +struct InsertCharityContext +{ + /** + * Charity name + */ + const char *charity_name; + + /** + * Charity URL + */ + const char *charity_url; + +}; + + +/** + * Function implementing insert charity transaction. + * + * 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 closure with a `struct InsertCharityContext` + * @param connection MHD request which triggered the transaction + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +insert_charity (void *cls, + struct MHD_Connection *connection, + MHD_RESULT *mhd_ret) +{ + struct InsertCharityContext *icc = cls; + enum GNUNET_DB_QueryStatus qs; + + qs = DH_plugin->insert_charity (DH_plugin->cls, + icc->charity_name, + icc->charity_url); + if (qs <= 0) + { + if (GNUNET_DB_STATUS_SOFT_ERROR != qs) + { + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "insert_charity"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + return qs; + } + + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +} + + +MHD_RESULT +DH_handler_charity_post ( + struct MHD_Connection *connection, + const json_t *root) +{ + struct InsertCharityContext icc; + + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("charity_url", + &icc.charity_url), + GNUNET_JSON_spec_array_const ("charity_name", + &icc.charity_name), + GNUNET_JSON_spec_end () + }; + + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + root, + spec); + if (GNUNET_SYSERR == res) + return MHD_NO; /* hard failure */ + if (GNUNET_NO == res) + { + GNUNET_break_op (0); + return MHD_YES; /* failure */ + } + } + + { + MHD_RESULT mhd_ret; + + if (GNUNET_OK != + DH_DB_run_transaction (connection, + "insert_charity", + DH_MT_REQUEST_OTHER, + &mhd_ret, + &insert_charity, + &icc)) + { + return mhd_ret; + } + } + return TALER_MHD_reply_static ( + connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); +} + + +/* end of donau-httpd_post-charity.c */ diff --git a/src/donaudb/0002-donation_units.sql b/src/donaudb/0002-donation_units.sql @@ -16,7 +16,7 @@ CREATE TABLE donation_units (donation_unit_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE - ,donation_unit_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(donation_unit_pub_hash)=64) + ,donation_unit_hash BYTEA PRIMARY KEY CHECK (LENGTH(donation_unit_hash)=64) ,donation_unit_pub BYTEA NOT NULL ,validity_year INT4 NOT NULL ,donation_unit taler_amount NOT NULL diff --git a/src/donaudb/0002-donau_charities.sql b/src/donaudb/0002-donau_charities.sql @@ -16,8 +16,9 @@ CREATE TABLE charities (charity_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE - ,charity_name TEXT NOT NULL ,charity_pub BYTEA PRIMARY KEY CHECK (LENGTH(donau_pub)=32) + ,charity_name TEXT NOT NULL + ,charity_url TEXT NOT NULL ,max_per_year taler_amount NOT NULL ,receipts_to_date taler_amount NOT NULL DEFAULT (0,0) ,current_year INT8 NOT NULL diff --git a/src/donaudb/Makefile.am b/src/donaudb/Makefile.am @@ -57,8 +57,31 @@ plugin_LTLIBRARIES = \ libtaler_plugin_donaudb_postgres.la endif + libtaler_plugin_donaudb_postgres_la_SOURCES = \ - plugin_donaudb_postgres.c pg_helper.h + plugin_donaudb_postgres.c pg_helper.h \ + pg_preflight.h pg_preflight.c \ + pg_commit.h pg_commit.c \ + pg_drop_tables.h pg_drop_tables.c \ + pg_create_tables.h pg_create_tables.c \ + pg_event_listen.h pg_event_listen.c \ + pg_event_listen_cancel.h pg_event_listen_cancel.c \ + pg_event_notify.h pg_event_notify.c \ + pg_start.h pg_start.c \ + pg_rollback.h pg_rollback.c \ + pg_start_read_committed.h pg_start_read_committed.c \ + pg_start_read_only.h pg_start_read_only.c \ + pg_activate_signing_key.c pg_activate_signing_key.h \ + pg_lookup_signing_key.h pg_lookup_signing_key.c \ + pg_add_donation_unit_key.c pg_add_donation_unit_key.h \ + pg_lookup_donation_unit.c pg_lookup_donation_unit.h \ + pg_get_charities.h pg_get_charities.c \ + pg_insert_charity.h pg_insert_charity.c \ + pg_lookup_charity.h pg_lookup_charity.c \ + pg_insert_history_entry.h pg_insert_history_entry.c \ + pg_insert_issued_receipt.h pg_insert_issued_receipt.c \ + pg_insert_submitted_receipt.h pg_insert_submitted_receipt.c + libtaler_plugin_donaudb_postgres_la_LIBADD = \ $(LTLIBINTL) libtaler_plugin_donaudb_postgres_la_LDFLAGS = \ diff --git a/src/donaudb/pg_activate_signing_key.c b/src/donaudb/pg_activate_signing_key.c @@ -28,7 +28,7 @@ enum GNUNET_DB_QueryStatus DH_PG_activate_signing_key ( void *cls, - const struct DONAU_EddsaPublicKeyP *donau_pub, + const struct DONAU_DonauPublicKeyP *donau_pub, const struct DONAUDB_SignkeyMetaData *meta) { struct PostgresClosure *pg = cls; diff --git a/src/donaudb/pg_activate_signing_key.h b/src/donaudb/pg_activate_signing_key.h @@ -36,7 +36,7 @@ enum GNUNET_DB_QueryStatus DH_PG_activate_signing_key ( void *cls, - const struct DONAU_EddsaPublicKeyP *donau_pub, + const struct DONAU_DonauPublicKeyP *donau_pub, const struct DONAUDB_SignkeyMetaData *meta); #endif diff --git a/src/donaudb/pg_add_donation_unit_key.c b/src/donaudb/pg_add_donation_unit_key.c @@ -34,17 +34,18 @@ DH_PG_add_donation_unit_key ( { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam iparams[] = { - GNUNET_PQ_query_param_auto_from_type (&meta->donation_unit_pub_hash), - TALER_PQ_query_param_donation_unit_pub (donation_unit_pub), + GNUNET_PQ_query_param_auto_from_type (&meta->donation_unit_hash), + GNUNET_PQ_query_param_auto_from_type (donation_unit_pub), GNUNET_PQ_query_param_uint64 (&meta->validity_year), - TALER_PQ_query_param_amount (&meta->value), + TALER_PQ_query_param_amount (pg->conn, + &meta->value), GNUNET_PQ_query_param_end }; PREPARE (pg, "donation_unit_insert", "INSERT INTO donation_units " - "(donation_unit_pub_hash" + "(donation_unit_hash" ",donation_unit_pub" ",validity_year" ",taler_amount" diff --git a/src/donaudb/pg_add_donation_unit_key.h b/src/donaudb/pg_add_donation_unit_key.h @@ -37,5 +37,6 @@ enum GNUNET_DB_QueryStatus DH_PG_add_donation_unit_key ( void *cls, const struct DONAU_DonationUnitPublicKey *donation_unit_pub, - const struct DONAUDB_DonationUnitKeyMetaInfo *meta) + const struct DONAUDB_DonationUnitKeyMetaData *meta); + #endif diff --git a/src/donaudb/pg_get_charities.c b/src/donaudb/pg_get_charities.c @@ -0,0 +1,126 @@ +/* + This file is part of TALER + Copyright (C) 2024 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 CHARITYABILITY 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 donaudb/pg_get_charities.c + * @brief Implementation of the lookup_donation_unit_key function for Postgres + * @author Johannes Casaburi + */ +#include "taler/platform.h" +#include "taler/taler_error_codes.h" +#include "taler/taler_dbevents.h" +#include "taler/taler_pq_lib.h" +#include "pg_get_charities.h" +#include "pg_helper.h" + + +/** + * Closure for #get_charities_cb(). + */ +struct GetCharitiesContext +{ + /** + * Function to call per result. + */ + DONAUDB_GetCharitiesCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Flag set to #GNUNET_OK as long as everything is fine. + */ + enum GNUNET_GenericReturnValue status; + +}; + + +/** + * Invoke the callback for each result. + * + * @param cls a `struct MissingWireContext *` + * @param result SQL result + * @param num_results number of rows in @a result + */ +static void +get_charities_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct GetCharitiesContext *ctx = cls; + + for (unsigned int i = 0; i < num_results; i++) + { + char *charity_name; + char *charity_url; + + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_string ("charity_name", + &charity_name), + GNUNET_PQ_result_spec_string ("charity_url", + &charity_url), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ctx->status = GNUNET_SYSERR; + return; + } + ctx->cb (ctx->cb_cls, + charity_name, + charity_url); + GNUNET_PQ_cleanup_result (rs); + } +} + + +enum GNUNET_DB_QueryStatus +DH_PG_get_charities (void *cls, + DONAUDB_GetCharitiesCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + struct GetCharitiesContext ctx = { + .cb = cb, + .cb_cls = cb_cls, + .status = GNUNET_OK + }; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs; + + PREPARE (pg, + "get_charities", + "SELECT" + " charity_name" + ",charity_url" + " FROM charities"); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "get_charities", + params, + &get_charities_cb, + &ctx); + if (GNUNET_OK != ctx.status) + return GNUNET_DB_STATUS_HARD_ERROR; + return qs; +} diff --git a/src/donaudb/pg_get_charities.h b/src/donaudb/pg_get_charities.h @@ -0,0 +1,39 @@ +/* + This file is part of TALER + Copyright (C) 2024 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 CHARITYABILITY 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 donaudb/pg_get_charities.h + * @brief implementation of the get_charities function for Postgres + * @author Johannes Casaburi + */ +#ifndef PG_GET_CHARITIES_H +#define PG_GET_CHARITIES_H + +#include "donaudb_plugin.h" + +/** + * Obtain information about the enabled wire accounts of the exchange. + * + * @param cls closure + * @param cb function to call on each account + * @param cb_cls closure for @a cb + * @return transaction status code + */ +enum GNUNET_DB_QueryStatus +DH_PG_get_charities (void *cls, + DONAUDB_GetCharitiesCallback cb, + void *cb_cls); + +#endif diff --git a/src/donaudb/pg_get_donation_unit_info.c b/src/donaudb/pg_get_donation_unit_info.c @@ -1,67 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2023 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 CHARITYABILITY 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 donaudb/pg_get_donation_unit_info.c - * @brief Implementation of the get_donation_unit_info function for Postgres - * @author Johannes Casaburi - */ -#include "taler/platform.h" -#include "taler/taler_error_codes.h" -#include "taler/taler_dbevents.h" -#include "taler/taler_pq_lib.h" -#include "pg_get_donation_unit_info.h" -#include "pg_helper.h" - - -enum GNUNET_DB_QueryStatus -DH_PG_get_donation_unit_info ( - void *cls, - const struct DONAU_DonationUnitHashP *donation_unit_pub_hash, - struct DONAUDB_DonationUnitKeyMetaData *meta) -{ - struct PostgresClosure *pg = cls; - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (denom_pub_hash), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("donation_unit_pub", - &meta->donation_unit_pub), - TALER_PQ_RESULT_SPEC_AMOUNT ("value", - &meta->donation_unit), - GNUNET_PQ_result_spec_uint32 ("validity_year", - &meta->validity_year), - GNUNET_PQ_result_spec_end - }; - - PREPARE (pg, - "donation_unit_get", - "SELECT" - ",donation_unit_pub" - ",validity_year" - ",value" - " FROM donation_units" - " WHERE donation_unit_pub_hash=$1;"); - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "donation_unit_get", - params, - rs); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - return qs; - meta->donation_unit_hash = *donation_unit_pub_hash; - return qs; -} diff --git a/src/donaudb/pg_get_donation_unit_info.h b/src/donaudb/pg_get_donation_unit_info.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 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 CHARITYABILITY 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 donaudb/pg_get_donation_unit_info.h - * @brief implementation of the get_donation_unit_info function for Postgres - * @author Johannes Casaburi - */ -#ifndef PG_GET_DONATION_UNIT_INFO_H -#define PG_GET_DONATION_UNIT_INFO_H - -#include <taler/taler_util.h> -#include "taler/taler_json_lib.h" -#include "donaudb_plugin.h" -/** - * Fetch information about a donation unit key. - * - * @param cls the @e cls of this struct with the plugin-specific state - * @param donation_unit_pub_hash hash of the public key - * @param[out] info information with value and other info about the coin - * @return transaction status code - */ -enum GNUNET_DB_QueryStatus -DH_PG_get_donation_unit_info ( - void *cls, - const struct DONAU_DonationUnitHashP *donation_unit_pub_hash, - struct DONAUDB_DonationUnitKeyMetaData *meta); - -#endif diff --git a/src/donaudb/pg_helper.h b/src/donaudb/pg_helper.h @@ -60,6 +60,11 @@ struct PostgresClosure */ unsigned long long prep_gen; + /** + * Name of the current transaction, for debugging. + */ + const char *transaction_name; + }; @@ -73,37 +78,37 @@ struct PostgresClosure * @param sql actual SQL text */ #define PREPARE(pg,name,sql) \ - do { \ - static struct { \ - unsigned long long cnt; \ - struct PostgresClosure *pg; \ - } preps[2]; /* 2 ctrs for taler-auditor-sync*/ \ - unsigned int off = 0; \ + do { \ + static struct { \ + unsigned long long cnt; \ + struct PostgresClosure *pg; \ + } preps[2]; /* 2 ctrs for taler-auditor-sync*/ \ + unsigned int off = 0; \ \ - while ( (NULL != preps[off].pg) && \ - (pg != preps[off].pg) && \ - (off < sizeof(preps) / sizeof(*preps)) ) \ - off++; \ - GNUNET_assert (off < \ - sizeof(preps) / sizeof(*preps)); \ - if (preps[off].cnt < pg->prep_gen) \ - { \ - struct GNUNET_PQ_PreparedStatement ps[] = { \ - GNUNET_PQ_make_prepare (name, sql), \ - GNUNET_PQ_PREPARED_STATEMENT_END \ - }; \ + while ( (NULL != preps[off].pg) && \ + (pg != preps[off].pg) && \ + (off < sizeof(preps) / sizeof(*preps)) ) \ + off++; \ + GNUNET_assert (off < \ + sizeof(preps) / sizeof(*preps)); \ + if (preps[off].cnt < pg->prep_gen) \ + { \ + struct GNUNET_PQ_PreparedStatement ps[] = { \ + GNUNET_PQ_make_prepare (name, sql), \ + GNUNET_PQ_PREPARED_STATEMENT_END \ + }; \ \ - if (GNUNET_OK != \ - GNUNET_PQ_prepare_statements (pg->conn, \ - ps)) \ - { \ - GNUNET_break (0); \ - return GNUNET_DB_STATUS_HARD_ERROR; \ - } \ - preps[off].pg = pg; \ - preps[off].cnt = pg->prep_gen; \ - } \ - } while (0) + if (GNUNET_OK != \ + GNUNET_PQ_prepare_statements (pg->conn, \ + ps)) \ + { \ + GNUNET_break (0); \ + return GNUNET_DB_STATUS_HARD_ERROR; \ + } \ + preps[off].pg = pg; \ + preps[off].cnt = pg->prep_gen; \ + } \ + } while (0) /** @@ -115,7 +120,7 @@ struct PostgresClosure */ #define TALER_PQ_RESULT_SPEC_AMOUNT(field, \ amountp) TALER_PQ_result_spec_amount ( \ - field,pg->currency,amountp) + field,pg->currency,amountp) #endif diff --git a/src/donaudb/pg_insert_charity.c b/src/donaudb/pg_insert_charity.c @@ -28,8 +28,9 @@ enum GNUNET_DB_QueryStatus DH_PG_insert_charity (void *cls, const struct DONAU_CharityPublicKeyP *charity_pub, - const char *charity_url, const char *charity_name, + const char *charity_url, + struct TALER_Amount *max_per_year, struct TALER_Amount *receipts_to_date, uint64_t current_year) { @@ -38,7 +39,10 @@ DH_PG_insert_charity (void *cls, GNUNET_PQ_query_param_auto_from_type (charity_pub), GNUNET_PQ_query_param_string (charity_name), GNUNET_PQ_query_param_string (charity_url), - TALER_PQ_query_param_amount (receipts_to_date), + TALER_PQ_query_param_amount (pg->conn, + max_per_year), + TALER_PQ_query_param_amount (pg->conn, + receipts_to_date), GNUNET_PQ_query_param_uint64 (&current_year), GNUNET_PQ_query_param_end }; @@ -49,10 +53,11 @@ DH_PG_insert_charity (void *cls, "(charity_pub" ",charity_name" ",charity_url" + ",max_per_year" ",receipts_to_date" ",current_year" ") VALUES " - "($1, $2, $3, $4, $5);"); + "($1, $2, $3, $4, $5, $6);"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_charity", params); diff --git a/src/donaudb/pg_insert_charity.h b/src/donaudb/pg_insert_charity.h @@ -32,6 +32,7 @@ * @param charity_pub charity public key * @param charity_name name * @param charity_url url + * @param max_per_year yearly donation limit * @param receipts_to_date current amount of donations in the current year * @param current_year current year * @return transaction status code @@ -39,9 +40,10 @@ enum GNUNET_DB_QueryStatus DH_PG_insert_charity ( void *cls, - const struct DONAU_CharityPublicKey *charity_pub, + const struct DONAU_CharityPublicKeyP *charity_pub, const char *charity_name, const char *charity_url, + struct TALER_Amount *max_per_year, struct TALER_Amount *receipts_to_date, uint64_t current_year); diff --git a/src/donaudb/pg_insert_history_entry.c b/src/donaudb/pg_insert_history_entry.c @@ -34,7 +34,8 @@ DH_PG_insert_history_entry (void *cls, struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (charity_id), - GNUNET_PQ_query_param_amount (final_amount), + TALER_PQ_query_param_amount (pg->conn, + final_amount), GNUNET_PQ_query_param_uint64 (donation_year), GNUNET_PQ_query_param_end }; diff --git a/src/donaudb/pg_insert_issued_receipt.c b/src/donaudb/pg_insert_issued_receipt.c @@ -24,12 +24,13 @@ #include "taler/taler_pq_lib.h" #include "pg_insert_issued_receipt.h" #include "pg_helper.h" +#include "donau_service.h" enum GNUNET_DB_QueryStatus DH_PG_insert_issued_receipt (void *cls, const struct DONAU_CharitySignatureP *charity_sig, const uint64_t charity_id, - const struct DONAU_DonationReceiptHashP h_receipt, + const struct DONAU_DonationReceiptHashP *h_receipt, const struct TALER_Amount *amount) { struct PostgresClosure *pg = cls; @@ -37,7 +38,8 @@ DH_PG_insert_issued_receipt (void *cls, GNUNET_PQ_query_param_auto_from_type (charity_sig), GNUNET_PQ_query_param_uint64 (charity_id), GNUNET_PQ_query_param_auto_from_type (h_receipt), - GNUNET_PQ_query_param_amount (amount), + TALER_PQ_query_param_amount (pg->conn, + amount), GNUNET_PQ_query_param_end }; diff --git a/src/donaudb/pg_insert_issued_receipt.h b/src/donaudb/pg_insert_issued_receipt.h @@ -39,8 +39,7 @@ enum GNUNET_DB_QueryStatus DH_PG_insert_issued_receipt (void *cls, const struct DONAU_CharitySignatureP *charity_sig, const uint64_t charity_id, - const struct - DONAU_DonationReceiptHashP *h_receipt, + const struct DONAU_DonationReceiptHashP *h_receipt, const struct TALER_Amount *amount); #endif diff --git a/src/donaudb/pg_insert_submitted_receipt.c b/src/donaudb/pg_insert_submitted_receipt.c @@ -24,12 +24,13 @@ #include "taler/taler_pq_lib.h" #include "pg_insert_submitted_receipt.h" #include "pg_helper.h" +#include "donau_service.h" enum GNUNET_DB_QueryStatus DH_PG_insert_submitted_receipt (void *cls, - const struct - TALER_TaxNumberHashP *h_tax_number, - const struct TALER_CsNonce *nonce, + const struct DONAU_HashDonorTaxId *h_tax_number, + const union GNUNET_CRYPTO_BlindSessionNonce * + nonce, const struct DONAU_DonationUnitPublicKey *donation_unit_pub, const struct TALER_DonauSignatureP *donau_sig, diff --git a/src/donaudb/pg_insert_submitted_receipt.h b/src/donaudb/pg_insert_submitted_receipt.h @@ -24,6 +24,7 @@ #include <taler/taler_util.h> #include "taler/taler_json_lib.h" #include "donaudb_plugin.h" +#include "donau_service.h" /** * Insert submitted donation receipt from the donor. @@ -38,9 +39,9 @@ */ enum GNUNET_DB_QueryStatus DH_PG_insert_submitted_receipt (void *cls, - const struct - TALER_TaxNumberHashP *h_tax_number, - const struct TALER_CsNonce *nonce, + const struct DONAU_HashDonorTaxId *h_tax_number, + const union GNUNET_CRYPTO_BlindSessionNonce * + nonce, const struct DONAU_DonationUnitPublicKey *donation_unit_pub, const struct TALER_DonauSignatureP *donau_sig, diff --git a/src/donaudb/pg_lookup_charity.c b/src/donaudb/pg_lookup_charity.c @@ -28,7 +28,8 @@ enum GNUNET_DB_QueryStatus DH_PG_lookup_charity ( void *cls, - struct DONAUDB_CharityMetaData *meta) + unsigned long long charity_id, + const struct DONAUDB_CharityMetaData *meta) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -36,9 +37,9 @@ DH_PG_lookup_charity ( GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_query_param_string ("charity_url", - &meta->charity_url), - GNUNET_PQ_query_param_string ("charity_name", + GNUNET_PQ_result_spec_string ("charity_url", + &meta->charity_url), + GNUNET_PQ_result_spec_string ("charity_name", &meta->charity_name), GNUNET_PQ_result_spec_end }; diff --git a/src/donaudb/pg_lookup_charity.h b/src/donaudb/pg_lookup_charity.h @@ -28,13 +28,13 @@ * Fetch information about a donation unit key. * * @param cls the @e cls of this struct with the plugin-specific state - * @param donation_unit_pub_hash hash of the public key * @param[out] info information with value and other info about the coin * @return transaction status code */ enum GNUNET_DB_QueryStatus DH_PG_lookup_charity ( void *cls, - struct DONAUDB_CharityMetaData *meta); + unsigned long long charity_id, + const struct DONAUDB_CharityMetaData *meta); #endif diff --git a/src/donaudb/pg_lookup_donation_unit.c b/src/donaudb/pg_lookup_donation_unit.c @@ -0,0 +1,58 @@ +/* + This file is part of TALER + Copyright (C) 2023 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 CHARITYABILITY 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 donaudb/pg_lookup_donation_unit_key.c + * @brief Implementation of the lookup_donation_unit_key function for Postgres + * @author Johannes Casaburi + */ +#include "taler/platform.h" +#include "taler/taler_error_codes.h" +#include "taler/taler_dbevents.h" +#include "taler/taler_pq_lib.h" +#include "pg_lookup_donation_unit.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +DH_PG_lookup_donation_unit_key ( + void *cls, + const struct DONAU_DonationUnitHashP *h_donation_unit_pub, + struct DONAUDB_DonationUnitKeyMetaData *meta) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (h_donation_unit_pub), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("validity_year", + &meta->validity_year), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount", + &meta->value), + GNUNET_PQ_result_spec_end + }; + + PREPARE (pg, + "lookup_donation_unit_key", + "SELECT" + " validity_year" + ",amount" + " FROM donation_units" + " WHERE donation_unit_hash=$1;"); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_donation_unit_key", + params, + rs); +} diff --git a/src/donaudb/pg_lookup_donation_unit_key.h b/src/donaudb/pg_lookup_donation_unit.h diff --git a/src/donaudb/pg_lookup_donation_unit_key.c b/src/donaudb/pg_lookup_donation_unit_key.c @@ -1,61 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2023 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 CHARITYABILITY 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 donaudb/pg_lookup_donation_unit_key.c - * @brief Implementation of the lookup_donation_unit_key function for Postgres - * @author Johannes Casaburi - */ -#include "taler/platform.h" -#include "taler/taler_error_codes.h" -#include "taler/taler_dbevents.h" -#include "taler/taler_pq_lib.h" -#include "pg_lookup_donation_unit_key.h" -#include "pg_helper.h" - -enum GNUNET_DB_QueryStatus -DH_PG_lookup_donation_unit_key ( - void *cls, - const struct DONAU_DonationUnitHashP *h_donation_unit_pub, - struct DONAUDB_DonationUnitKeyMetaData *meta) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (h_donation_unit_pub), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("donation_unit_pub", - &meta->donation_unit_pub), - GNUNET_PQ_query_param_uint64 ("validity_year", - &meta->validity_year), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount", - &meta->value), - GNUNET_PQ_result_spec_end - }; - - PREPARE (pg, - "lookup_donation_unit_key", - "SELECT" - ",donation_unit_pub" - " validity_year" - ",amount" - " FROM donation_units" - " WHERE donation_unit_pub_hash=$1;"); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "lookup_donation_unit_key", - params, - rs); -} diff --git a/src/donaudb/pg_lookup_signing_key.c b/src/donaudb/pg_lookup_signing_key.c @@ -29,7 +29,7 @@ enum GNUNET_DB_QueryStatus DH_PG_lookup_signing_key ( void *cls, - const struct DONAU_EddsaPublicKeyP *donau_pub, + const struct DONAU_DonauPublicKeyP *donau_pub, struct DONAUDB_SignkeyMetaData *meta) { struct PostgresClosure *pg = cls; diff --git a/src/donaudb/pg_lookup_signing_key.h b/src/donaudb/pg_lookup_signing_key.h @@ -37,7 +37,7 @@ enum GNUNET_DB_QueryStatus DH_PG_lookup_signing_key ( void *cls, - const struct DONAU_EddsaPublicKeyP *donau_pub, + const struct DONAU_DonauPublicKeyP *donau_pub, struct DONAUDB_SignkeyMetaData *meta); #endif diff --git a/src/include/donau_crypto_lib.h b/src/include/donau_crypto_lib.h @@ -31,6 +31,7 @@ #include <gnunet/gnunet_util_lib.h> #include "taler/taler_error_codes.h" +#include "taler/taler_util.h" #include <gcrypt.h> #include <jansson.h> @@ -118,6 +119,7 @@ struct DONAU_DonationUnitHashP }; /** +<<<<<<< HEAD * Compare two donation unit public keys. * * @param donation_unit1 first key @@ -148,6 +150,18 @@ void DONAU_donation_unit_pub_free (struct DONAU_DonationUnitPublicKey *donation_unit_pub); /** + * Hash used to represent a Donation Receipt + */ +struct DONAU_DonationReceiptHashP +{ + /** + * Actual hash value. + */ + struct GNUNET_HashCode hash; +}; + + +/** * Donor's hashed and salted unique donation identifier. */ struct DONAU_HashDonorTaxId diff --git a/src/include/donau_json_lib.h b/src/include/donau_json_lib.h @@ -0,0 +1,43 @@ +/* + This file is part of TALER + Copyright (C) 2024 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 include/donau_json_lib.h + * @brief helper functions for JSON processing using libjansson + * @author Lukas Matyja + */ +#ifndef TALER_JSON_LIB_H_ +#define TALER_JSON_LIB_H_ + +#include <jansson.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_util.h" +#include "taler/taler_error_codes.h" + +/** + * Generate a parser specification for a donation unit public key of a given + * cipher. + * + * @param field name of the field + * @param cipher which cipher type to parse for + * @param[out] pk key to fill + * @return corresponding field spec + */ +struct GNUNET_JSON_Specification +DONAU_JSON_spec_donation_unit_pub_cipher ( + const char *field, + enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher, + struct DONAU_DonationUnitPublicKey *pk); +\ No newline at end of file diff --git a/src/include/donau_service.h b/src/include/donau_service.h @@ -42,7 +42,7 @@ struct DONAU_SigningPublicKeyAndValidity /** * The signing public key */ - struct DONAU_EddsaPublicKeyP key; + struct DONAU_DonauPublicKeyP key; /** * Tax year this key is valid for. @@ -316,7 +316,7 @@ struct DONAU_GetKeysHandle * DONAU_get_keys ( struct GNUNET_CURL_Context *ctx, const char *url, - //struct DONAU_Keys *last_keys, -> temporarily removed + // struct DONAU_Keys *last_keys, -> temporarily removed DONAU_GetKeysCallback cert_cb, void *cert_cb_cls); @@ -373,7 +373,7 @@ DONAU_keys_decref (struct DONAU_Keys *keys); /** * Test if the given @a pub is a current signing key from the donau - * according to @a keys. + * according to @a keys. (-> // always current, revocation not yet supported) * * @param keys the donau's key set * @param pub claimed online signing key for the donau @@ -381,10 +381,10 @@ DONAU_keys_decref (struct DONAU_Keys *keys); * @return #GNUNET_OK if @a pub is (according to /keys and @a year) the corresponding signing key */ // enum GNUNET_GenericReturnValue -// DONAU_test_signing_key ( // always current revocation not yet supported +// DONAU_test_signing_key ( // const struct DONAU_Keys *keys, // const unsigned int year, -// const struct DONAU_EddsaPublicKeyP *pub); +// const struct DONAU_DonauPublicKeyP *pub); /** @@ -426,7 +426,7 @@ DONAU_get_donation_unit_key_by_hash ( const struct DONAU_SigningPublicKeyAndValidity * DONAU_get_signing_key_info ( const struct DONAU_Keys *keys, - const struct DONAU_EddsaPublicKeyP *donau_pub); + const struct DONAU_DonauPublicKeyP *donau_pub); /* ********************* POST / issue receipt *********************** */ @@ -552,6 +552,18 @@ struct TALER_DonationUnitSignature }; /** + * Donau signature + */ +struct TALER_DonauSignatureP +{ + /** + * The signature + */ + struct TALER_ExchangeSignatureP sig; + +}; + +/** * Donation Receipt */ struct DONAU_DonationReceipt @@ -1062,7 +1074,7 @@ struct DONAU_CharityRequest /** * public key of the charity */ - struct DONAU_EddsaPublicKeyP charity_pub; + struct DONAU_DonauPublicKeyP charity_pub; }; /** diff --git a/src/include/donaudb_plugin.h b/src/include/donaudb_plugin.h @@ -75,6 +75,39 @@ struct DONAUDB_SignkeyMetaData }; /** + * Meta data about a charity. + */ +struct DONAUDB_CharityMetaData +{ + /** + * Charity public key + */ + const struct DONAU_CharityPublicKeyP *charity_pub; + + /** + * Charity name + */ + const char *charity_name; + + /** + * Charity url + */ + const char *charity_url; + + /** + * Charity yearly donation limit + */ + struct TALER_Amount *max_per_year; + + /** + * Charity donations received in the current year + */ + struct TALER_Amount *receipts_to_date; + +}; + + +/** * @brief All information about a donation unit key. */ struct DONAUDB_DonationUnitKey @@ -124,6 +157,19 @@ typedef void /** + * Return charities. + * + * @param cls closure + * @param charity_url + * @param charity_name + */ +typedef void +(*DONAUDB_GetCharitiesCallback)( + void *cls, + const char *charity_name, + const char *charity_url); + +/** * @brief The plugin API, returned from the plugin's "init" function. * The argument given to "init" is simply a configuration handle. */ @@ -149,7 +195,7 @@ struct DONAUDB_Plugin * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ enum GNUNET_GenericReturnValue - (*drop_tables)(void *cls); + (*drop_tables)(void *cls); /** * Create the necessary tables if they are not present @@ -162,9 +208,9 @@ struct DONAUDB_Plugin * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ enum GNUNET_GenericReturnValue - (*create_tables)(void *cls, - bool support_partitions, - uint32_t num_partitions); + (*create_tables)(void *cls, + bool support_partitions, + uint32_t num_partitions); /** @@ -176,8 +222,8 @@ struct DONAUDB_Plugin * @return #GNUNET_OK on success */ enum GNUNET_GenericReturnValue - (*start)(void *cls, - const char *name); + (*start)(void *cls, + const char *name); /** @@ -189,8 +235,8 @@ struct DONAUDB_Plugin * @return #GNUNET_OK on success */ enum GNUNET_GenericReturnValue - (*start_read_committed)(void *cls, - const char *name); + (*start_read_committed)(void *cls, + const char *name); /** * Start a READ ONLY serializable transaction. @@ -201,8 +247,8 @@ struct DONAUDB_Plugin * @return #GNUNET_OK on success */ enum GNUNET_GenericReturnValue - (*start_read_only)(void *cls, - const char *name); + (*start_read_only)(void *cls, + const char *name); /** @@ -212,7 +258,7 @@ struct DONAUDB_Plugin * @return transaction status */ enum GNUNET_DB_QueryStatus - (*commit)(void *cls); + (*commit)(void *cls); /** @@ -226,7 +272,7 @@ struct DONAUDB_Plugin * #GNUNET_SYSERR on hard errors */ enum GNUNET_GenericReturnValue - (*preflight)(void *cls); + (*preflight)(void *cls); /** @@ -281,6 +327,50 @@ struct DONAUDB_Plugin const void *extra, size_t extra_size); + /** + * Get charity. + * + * @param cls closure + * @param charity_id + * @param charity_url + * @param charity_name + * @return database transaction status + */ + enum GNUNET_DB_QueryStatus + (*get_charity)( + void *cls, + unsigned long long charity_id, + const char *charity_url, + const char *charity_name); + +/** + * Get charities. + * + * @param cls closure + * @param cb callback to invoke on each match + * @param cb_cls closure for @a cb + * @return database transaction status + */ + enum GNUNET_DB_QueryStatus + (*get_charities)( + void *cls, + DONAUDB_GetCharitiesCallback cb, + void *cb_cls); + + /** + * Insert Charity + * + * @param cls closure + * @param charity_name + * @param charity_url + * @return database transaction status + */ + enum GNUNET_DB_QueryStatus + (*insert_charity)( + void *cls, + const char *charity_name, + const char *charity_url); + }; diff --git a/src/json/Makefile.am b/src/json/Makefile.am @@ -10,7 +10,10 @@ lib_LTLIBRARIES = \ libtalerjson.la libtalerjson_la_SOURCES = \ - json.c + json.c \ + json_helper.c \ + json_pack.c + libtalerjson_la_LDFLAGS = \ -version-info 1:0:1 \ -no-undefined diff --git a/src/json/donau_json.c b/src/json/donau_json.c @@ -0,0 +1,138 @@ +/* + This file is part of TALER + Copyright (C) 2024 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 json/donau_json.c + * @brief helper functions for JSON processing using libjansson + * @author Lukas Matyja + */ +#include "taler/platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler/taler_util.h" +#include "taler/taler_json_lib.h" +#include <unistr.h> +#include "donau_json_lib.h" +#include "donau_util.h" + +/** + * Parse given JSON object partially into a donation unit public key. + * + * Depending on the cipher in cls, it parses the corresponding public key type. + * + * @param cls closure, enum GNUNET_CRYPTO_BlindSignatureAlgorithm + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_donation_unit_pub_cipher (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct DONAU_DonationUnitPublicKey *donation_unit_pub = spec->ptr; + enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher = + (enum GNUNET_CRYPTO_BlindSignatureAlgorithm) (long) cls; + struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub; + const char *emsg; + unsigned int eline; + + bsign_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey); + bsign_pub->cipher = cipher; + bsign_pub->rc = 1; + switch (cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_rsa_public_key ( + "rsa_pub", + &bsign_pub->details.rsa_public_key), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_free (bsign_pub); + return GNUNET_SYSERR; + } + donation_unit_pub->bsign_pub_key = bsign_pub; + return GNUNET_OK; + } + case GNUNET_CRYPTO_BSA_CS: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed ("cs_pub", + &bsign_pub->details.cs_public_key, + sizeof (bsign_pub->details.cs_public_key)), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_free (bsign_pub); + return GNUNET_SYSERR; + } + donation_unit_pub->bsign_pub_key = bsign_pub; + return GNUNET_OK; + } + } + GNUNET_break_op (0); + GNUNET_free (bsign_pub); + return GNUNET_SYSERR; +} + +/** + * Cleanup data left from parsing donation unit public key. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_donation_unit_pub (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct DONAU_DonationUnitPublicKey *donation_unit_pub = spec->ptr; + + (void) cls; + DONAU_donation_unit_pub_free (donation_unit_pub); +} + +struct GNUNET_JSON_Specification +DONAU_JSON_spec_donation_unit_pub_cipher (const char *field, + enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher, + struct DONAU_DonationUnitPublicKey *pk) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_donation_unit_pub_cipher, + .cleaner = &clean_donation_unit_pub, + .field = field, + .cls = (void *) cipher, + .ptr = pk + }; + + return ret; +} +\ No newline at end of file diff --git a/src/json/json.c b/src/json/json.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023 Taler Systems SA + Copyright (C) 2024 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 @@ -21,9 +21,768 @@ */ #include "taler/platform.h" #include <gnunet/gnunet_util_lib.h> -#include <taler/taler_util.h> +#include "taler/taler_util.h" #include "taler/taler_json_lib.h" #include <unistr.h> +/** + * Check if @a json contains a 'real' value anywhere. + * + * @param json json to check + * @return true if a real is in it somewhere + */ +static bool +contains_real (const json_t *json) +{ + if (json_is_real (json)) + return true; + if (json_is_object (json)) + { + json_t *member; + const char *name; + + json_object_foreach ((json_t *) json, name, member) + if (contains_real (member)) + return true; + return false; + } + if (json_is_array (json)) + { + json_t *member; + size_t index; + + json_array_foreach ((json_t *) json, index, member) + if (contains_real (member)) + return true; + return false; + } + return false; +} + + +/** + * Dump the @a json to a string and hash it. + * + * @param json value to hash + * @param salt salt value to include when using HKDF, + * NULL to not use any salt and to use SHA512 + * @param[out] hc where to store the hash + * @return #GNUNET_OK on success, + * #GNUNET_NO if @a json was not hash-able + * #GNUNET_SYSERR on failure + */ +static enum GNUNET_GenericReturnValue +dump_and_hash (const json_t *json, + const char *salt, + struct GNUNET_HashCode *hc) +{ + char *wire_enc; + size_t len; + + if (NULL == json) + { + GNUNET_break_op (0); + return GNUNET_NO; + } + if (contains_real (json)) + { + GNUNET_break_op (0); + return GNUNET_NO; + } + if (NULL == (wire_enc = json_dumps (json, + JSON_ENCODE_ANY + | JSON_COMPACT + | JSON_SORT_KEYS))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + len = TALER_rfc8785encode (&wire_enc); + if (NULL == salt) + { + GNUNET_CRYPTO_hash (wire_enc, + len, + hc); + } + else + { + if (GNUNET_YES != + GNUNET_CRYPTO_kdf (hc, + sizeof (*hc), + salt, + strlen (salt) + 1, + wire_enc, + len, + NULL, + 0)) + { + GNUNET_break (0); + free (wire_enc); + return GNUNET_SYSERR; + } + } + free (wire_enc); + return GNUNET_OK; +} + + +/** + * Replace "forgettable" parts of a JSON object with their salted hash. + * + * @param[in] in some JSON value + * @param[out] out resulting JSON value + * @return #GNUNET_OK on success, + * #GNUNET_NO if @a json was not hash-able + * #GNUNET_SYSERR on failure + */ +static enum GNUNET_GenericReturnValue +forget (const json_t *in, + json_t **out) +{ + if (json_is_real (in)) + { + /* floating point is not allowed! */ + GNUNET_break_op (0); + return GNUNET_NO; + } + if (json_is_array (in)) + { + /* array is a JSON array */ + size_t index; + json_t *value; + json_t *ret; + + ret = json_array (); + if (NULL == ret) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + json_array_foreach (in, index, value) { + enum GNUNET_GenericReturnValue iret; + json_t *t; + + iret = forget (value, + &t); + if (GNUNET_OK != iret) + { + json_decref (ret); + return iret; + } + if (0 != json_array_append_new (ret, + t)) + { + GNUNET_break (0); + json_decref (ret); + return GNUNET_SYSERR; + } + } + *out = ret; + return GNUNET_OK; + } + if (json_is_object (in)) + { + json_t *ret; + const char *key; + json_t *value; + json_t *fg; + json_t *rx; + + fg = json_object_get (in, + "$forgettable"); + rx = json_object_get (in, + "$forgotten"); + if (NULL != rx) + { + rx = json_deep_copy (rx); /* should be shallow + by structure, but + deep copy is safer */ + if (NULL == rx) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + ret = json_object (); + if (NULL == ret) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + json_object_foreach ((json_t*) in, key, value) { + json_t *t; + json_t *salt; + enum GNUNET_GenericReturnValue iret; + + if (fg == value) + continue; /* skip! */ + if (rx == value) + continue; /* skip! */ + if ( (NULL != rx) && + (NULL != + json_object_get (rx, + key)) ) + { + (void) json_object_del (ret, + key); + continue; /* already forgotten earlier */ + } + iret = forget (value, + &t); + if (GNUNET_OK != iret) + { + json_decref (ret); + json_decref (rx); + return iret; + } + if ( (NULL != fg) && + (NULL != (salt = json_object_get (fg, + key))) ) + { + /* 't' is to be forgotten! */ + struct GNUNET_HashCode hc; + + if (! json_is_string (salt)) + { + GNUNET_break_op (0); + json_decref (ret); + json_decref (rx); + json_decref (t); + return GNUNET_NO; + } + iret = dump_and_hash (t, + json_string_value (salt), + &hc); + if (GNUNET_OK != iret) + { + json_decref (ret); + json_decref (rx); + json_decref (t); + return iret; + } + json_decref (t); + /* scrub salt */ + if (0 != + json_object_del (fg, + key)) + { + GNUNET_break_op (0); + json_decref (ret); + json_decref (rx); + return GNUNET_NO; + } + if (NULL == rx) + rx = json_object (); + if (NULL == rx) + { + GNUNET_break (0); + json_decref (ret); + return GNUNET_SYSERR; + } + if (0 != + json_object_set_new (rx, + key, + GNUNET_JSON_from_data_auto (&hc))) + { + GNUNET_break (0); + json_decref (ret); + json_decref (rx); + return GNUNET_SYSERR; + } + } + else + { + /* 't' to be used without 'forgetting' */ + if (0 != + json_object_set_new (ret, + key, + t)) + { + GNUNET_break (0); + json_decref (ret); + json_decref (rx); + return GNUNET_SYSERR; + } + } + } /* json_object_foreach */ + if ( (NULL != rx) && + (0 != + json_object_set_new (ret, + "$forgotten", + rx)) ) + { + GNUNET_break (0); + json_decref (ret); + return GNUNET_SYSERR; + } + *out = ret; + return GNUNET_OK; + } + *out = json_incref ((json_t *) in); + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_JSON_contract_hash (const json_t *json, + struct TALER_PrivateContractHashP *hc) +{ + enum GNUNET_GenericReturnValue ret; + json_t *cjson; + json_t *dc; + + dc = json_deep_copy (json); + ret = forget (dc, + &cjson); + json_decref (dc); + if (GNUNET_OK != ret) + return ret; + ret = dump_and_hash (cjson, + NULL, + &hc->hash); + json_decref (cjson); + return ret; +} + + +enum GNUNET_GenericReturnValue +TALER_JSON_contract_mark_forgettable (json_t *json, + const char *field) +{ + json_t *fg; + struct GNUNET_ShortHashCode salt; + + if (! json_is_object (json)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* check field name is legal for forgettable field */ + for (const char *f = field; '\0' != *f; f++) + { + char c = *f; + + if ( (c >= 'a') && (c <= 'z') ) + continue; + if ( (c >= 'A') && (c <= 'Z') ) + continue; + if ( (c >= '0') && (c <= '9') ) + continue; + if ('_' == c) + continue; + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (NULL == json_object_get (json, + field)) + { + /* field must exist */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + fg = json_object_get (json, + "$forgettable"); + if (NULL == fg) + { + fg = json_object (); + if (0 != + json_object_set_new (json, + "$forgettable", + fg)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &salt, + sizeof (salt)); + if (0 != + json_object_set_new (fg, + field, + GNUNET_JSON_from_data_auto (&salt))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_JSON_contract_part_forget (json_t *json, + const char *field) +{ + json_t *fg; + const json_t *part; + json_t *fp; + json_t *rx; + struct GNUNET_HashCode hc; + const char *salt; + enum GNUNET_GenericReturnValue ret; + + if (! json_is_object (json)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (NULL == (part = json_object_get (json, + field))) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Did not find field `%s' we were asked to forget\n", + field); + return GNUNET_SYSERR; + } + fg = json_object_get (json, + "$forgettable"); + if (NULL == fg) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Did not find '$forgettable' attribute trying to forget field `%s'\n", + field); + return GNUNET_SYSERR; + } + rx = json_object_get (json, + "$forgotten"); + if (NULL == rx) + { + rx = json_object (); + if (0 != + json_object_set_new (json, + "$forgotten", + rx)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + if (NULL != + json_object_get (rx, + field)) + { + if (! json_is_null (json_object_get (json, + field))) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Field `%s' market as forgotten, but still exists!\n", + field); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Already forgot field `%s'\n", + field); + return GNUNET_NO; + } + salt = json_string_value (json_object_get (fg, + field)); + if (NULL == salt) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Did not find required salt to forget field `%s'\n", + field); + return GNUNET_SYSERR; + } + + /* need to recursively forget to compute 'hc' */ + ret = forget (part, + &fp); + if (GNUNET_OK != ret) + return ret; + if (GNUNET_OK != + dump_and_hash (fp, + salt, + &hc)) + { + json_decref (fp); + GNUNET_break (0); + return GNUNET_SYSERR; + } + json_decref (fp); + /* drop salt */ + if (0 != + json_object_del (fg, + field)) + { + json_decref (fp); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + /* remember field as 'forgotten' */ + if (0 != + json_object_set_new (rx, + field, + GNUNET_JSON_from_data_auto (&hc))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* finally, set 'forgotten' field to null */ + if (0 != + json_object_del (json, + field)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Parse a json path. + * + * @param obj the object that the path is relative to. + * @param prev the parent of @e obj. + * @param path the path to parse. + * @param cb the callback to call, if we get to the end of @e path. + * @param cb_cls the closure for the callback. + * @return #GNUNET_OK on success, #GNUNET_SYSERR if @e path is malformed. + */ +static enum GNUNET_GenericReturnValue +parse_path (json_t *obj, + json_t *prev, + const char *path, + TALER_JSON_ExpandPathCallback cb, + void *cb_cls) +{ + char *id = GNUNET_strdup (path); + char *next_id = strchr (id, + '.'); + char *next_path; + char *bracket; + json_t *next_obj = NULL; + char *next_dot; + + GNUNET_assert (NULL != id); /* make stupid compiler happy */ + if (NULL == next_id) + { + cb (cb_cls, + id, + prev); + GNUNET_free (id); + return GNUNET_OK; + } + bracket = strchr (next_id, + '['); + *next_id = '\0'; + next_id++; + next_path = GNUNET_strdup (next_id); + next_dot = strchr (next_id, + '.'); + if (NULL != next_dot) + *next_dot = '\0'; + /* If this is the first time this is called, make sure id is "$" */ + if ( (NULL == prev) && + (0 != strcmp (id, + "$"))) + { + GNUNET_free (id); + GNUNET_free (next_path); + return GNUNET_SYSERR; + } + + /* Check for bracketed indices */ + if (NULL != bracket) + { + char *end_bracket = strchr (bracket, + ']'); + if (NULL == end_bracket) + { + GNUNET_free (id); + GNUNET_free (next_path); + return GNUNET_SYSERR; + } + *end_bracket = '\0'; + + *bracket = '\0'; + bracket++; + + json_t *array = json_object_get (obj, + next_id); + if (0 == strcmp (bracket, + "*")) + { + size_t index; + json_t *value; + int ret = GNUNET_OK; + + json_array_foreach (array, index, value) { + ret = parse_path (value, + obj, + next_path, + cb, + cb_cls); + if (GNUNET_OK != ret) + { + GNUNET_free (id); + GNUNET_free (next_path); + return ret; + } + } + } + else + { + unsigned int index; + char dummy; + + if (1 != sscanf (bracket, + "%u%c", + &index, + &dummy)) + { + GNUNET_free (id); + GNUNET_free (next_path); + return GNUNET_SYSERR; + } + next_obj = json_array_get (array, + index); + } + } + else + { + /* No brackets, so just fetch the object by name */ + next_obj = json_object_get (obj, + next_id); + } + + if (NULL != next_obj) + { + int ret = parse_path (next_obj, + obj, + next_path, + cb, + cb_cls); + GNUNET_free (id); + GNUNET_free (next_path); + return ret; + } + GNUNET_free (id); + GNUNET_free (next_path); + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_JSON_expand_path (json_t *json, + const char *path, + TALER_JSON_ExpandPathCallback cb, + void *cb_cls) +{ + return parse_path (json, + NULL, + path, + cb, + cb_cls); +} + + +enum TALER_ErrorCode +TALER_JSON_get_error_code (const json_t *json) +{ + const json_t *jc; + + if (NULL == json) + return TALER_EC_GENERIC_INVALID_RESPONSE; + jc = json_object_get (json, "code"); + /* The caller already knows that the JSON represents an error, + so we are dealing with a missing error code here. */ + if (NULL == jc) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Expected Taler error code `code' in JSON, but field does not exist!\n"); + return TALER_EC_INVALID; + } + if (json_is_integer (jc)) + return (enum TALER_ErrorCode) json_integer_value (jc); + GNUNET_break_op (0); + return TALER_EC_INVALID; +} + + +const char * +TALER_JSON_get_error_hint (const json_t *json) +{ + const json_t *jc; + + if (NULL == json) + return NULL; + jc = json_object_get (json, + "hint"); + if (NULL == jc) + return NULL; /* no hint, is allowed */ + if (! json_is_string (jc)) + { + /* Hints must be strings */ + GNUNET_break_op (0); + return NULL; + } + return json_string_value (jc); +} + + +enum TALER_ErrorCode +TALER_JSON_get_error_code2 (const void *data, + size_t data_size) +{ + json_t *json; + enum TALER_ErrorCode ec; + json_error_t err; + + json = json_loadb (data, + data_size, + JSON_REJECT_DUPLICATES, + &err); + if (NULL == json) + return TALER_EC_INVALID; + ec = TALER_JSON_get_error_code (json); + json_decref (json); + if (ec == TALER_EC_NONE) + return TALER_EC_INVALID; + return ec; +} + + +void +TALER_deposit_policy_hash (const json_t *policy, + struct TALER_ExtensionPolicyHashP *ech) +{ + GNUNET_assert (GNUNET_OK == + dump_and_hash (policy, + "taler-extensions-policy", + &ech->hash)); +} + + +char * +TALER_JSON_canonicalize (const json_t *input) +{ + char *wire_enc; + + if (NULL == (wire_enc = json_dumps (input, + JSON_ENCODE_ANY + | JSON_COMPACT + | JSON_SORT_KEYS))) + { + GNUNET_break (0); + return NULL; + } + TALER_rfc8785encode (&wire_enc); + return wire_enc; +} + + +enum GNUNET_GenericReturnValue +TALER_JSON_extensions_manifests_hash (const json_t *manifests, + struct TALER_ExtensionManifestsHashP *ech) +{ + return dump_and_hash (manifests, + "taler-extensions-manifests", + &ech->hash); +} + + /* End of json/json.c */ diff --git a/src/json/json_helper.c b/src/json/json_helper.c @@ -0,0 +1,1621 @@ +/* + This file is part of TALER + Copyright (C) 2014-2024 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 json/json_helper.c + * @brief helper functions to generate specifications to parse + * Taler-specific JSON objects with libgnunetjson + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler/taler_util.h" +#include "taler/taler_json_lib.h" + + +/** + * Convert string value to numeric cipher value. + * + * @param cipher_s input string + * @return numeric cipher value + */ +static enum GNUNET_CRYPTO_BlindSignatureAlgorithm +string_to_cipher (const char *cipher_s) +{ + if ((0 == strcasecmp (cipher_s, + "RSA")) || + (0 == strcasecmp (cipher_s, + "RSA+age_restricted"))) + return GNUNET_CRYPTO_BSA_RSA; + if ((0 == strcasecmp (cipher_s, + "CS")) || + (0 == strcasecmp (cipher_s, + "CS+age_restricted"))) + return GNUNET_CRYPTO_BSA_CS; + return GNUNET_CRYPTO_BSA_INVALID; +} + + +json_t * +TALER_JSON_from_amount (const struct TALER_Amount *amount) +{ + char *amount_str = TALER_amount_to_string (amount); + + GNUNET_assert (NULL != amount_str); + { + json_t *j = json_string (amount_str); + + GNUNET_free (amount_str); + return j; + } +} + + +/** + * Parse given JSON object to Amount + * + * @param cls closure, expected currency, or NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_amount (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + const char *currency = cls; + struct TALER_Amount *r_amount = spec->ptr; + + (void) cls; + if (! json_is_string (root)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_string_to_amount (json_string_value (root), + r_amount)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if ( (NULL != currency) && + (0 != + strcasecmp (currency, + r_amount->currency)) ) + { + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Expected currency `%s', but amount used currency `%s' in field `%s'\n", + currency, + r_amount->currency, + spec->field); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_amount (const char *name, + const char *currency, + struct TALER_Amount *r_amount) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_amount, + .cleaner = NULL, + .cls = (void *) currency, + .field = name, + .ptr = r_amount, + .ptr_size = 0, + .size_ptr = NULL + }; + + GNUNET_assert (NULL != currency); + return ret; +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_amount_any (const char *name, + struct TALER_Amount *r_amount) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_amount, + .cleaner = NULL, + .cls = NULL, + .field = name, + .ptr = r_amount, + .ptr_size = 0, + .size_ptr = NULL + }; + + return ret; +} + + +/** + * Parse given JSON object to currency spec. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_cspec (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_CurrencySpecification *r_cspec = spec->ptr; + const char *currency = spec->cls; + const char *name; + uint32_t fid; + uint32_t fnd; + uint32_t ftzd; + const json_t *map; + struct GNUNET_JSON_Specification gspec[] = { + GNUNET_JSON_spec_string ("name", + &name), + GNUNET_JSON_spec_uint32 ("num_fractional_input_digits", + &fid), + GNUNET_JSON_spec_uint32 ("num_fractional_normal_digits", + &fnd), + GNUNET_JSON_spec_uint32 ("num_fractional_trailing_zero_digits", + &ftzd), + GNUNET_JSON_spec_object_const ("alt_unit_names", + &map), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + memset (r_cspec->currency, + 0, + sizeof (r_cspec->currency)); + if (GNUNET_OK != + GNUNET_JSON_parse (root, + gspec, + &emsg, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + spec[eline].field, + eline, + emsg); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (strlen (currency) >= TALER_CURRENCY_LEN) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if ( (fid > TALER_AMOUNT_FRAC_LEN) || + (fnd > TALER_AMOUNT_FRAC_LEN) || + (ftzd > TALER_AMOUNT_FRAC_LEN) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_check_currency (currency)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + strcpy (r_cspec->currency, + currency); + if (GNUNET_OK != + TALER_check_currency_scale_map (map)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + r_cspec->name = GNUNET_strdup (name); + r_cspec->map_alt_unit_names = json_incref ((json_t *) map); + return GNUNET_OK; +} + + +/** + * Cleanup data left from parsing encrypted contract. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_cspec (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_CurrencySpecification *cspec = spec->ptr; + + (void) cls; + GNUNET_free (cspec->name); + json_decref (cspec->map_alt_unit_names); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_currency_specification ( + const char *name, + const char *currency, + struct TALER_CurrencySpecification *r_cspec) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_cspec, + .cleaner = &clean_cspec, + .cls = (void *) currency, + .field = name, + .ptr = r_cspec, + .ptr_size = sizeof (*r_cspec), + .size_ptr = NULL + }; + + memset (r_cspec, + 0, + sizeof (*r_cspec)); + return ret; +} + + +static enum GNUNET_GenericReturnValue +parse_denomination_group (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_DenominationGroup *group = spec->ptr; + const char *cipher; + const char *currency = cls; + bool age_mask_missing = false; + bool has_age_restricted_suffix = false; + struct GNUNET_JSON_Specification gspec[] = { + GNUNET_JSON_spec_string ("cipher", + &cipher), + TALER_JSON_spec_amount ("value", + currency, + &group->value), + TALER_JSON_SPEC_DENOM_FEES ("fee", + currency, + &group->fees), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("age_mask", + &group->age_mask.bits), + &age_mask_missing), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + gspec, + &emsg, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + spec[eline].field, + eline, + emsg); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + group->cipher = string_to_cipher (cipher); + if (GNUNET_CRYPTO_BSA_INVALID == group->cipher) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* age_mask and suffix must be consistent */ + has_age_restricted_suffix = + (NULL != strstr (cipher, "+age_restricted")); + if (has_age_restricted_suffix && age_mask_missing) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (age_mask_missing) + group->age_mask.bits = 0; + + return GNUNET_OK; +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_denomination_group (const char *name, + const char *currency, + struct TALER_DenominationGroup *group) +{ + struct GNUNET_JSON_Specification ret = { + .cls = (void *) currency, + .parser = &parse_denomination_group, + .field = name, + .ptr = group, + .ptr_size = sizeof(*group) + }; + + return ret; +} + + +/** + * Parse given JSON object to an encrypted contract. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_econtract (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_EncryptedContract *econtract = spec->ptr; + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_varsize ("econtract", + &econtract->econtract, + &econtract->econtract_size), + GNUNET_JSON_spec_fixed_auto ("econtract_sig", + &econtract->econtract_sig), + GNUNET_JSON_spec_fixed_auto ("contract_pub", + &econtract->contract_pub), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + (void) cls; + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Cleanup data left from parsing encrypted contract. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_econtract (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_EncryptedContract *econtract = spec->ptr; + + (void) cls; + GNUNET_free (econtract->econtract); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_econtract (const char *name, + struct TALER_EncryptedContract *econtract) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_econtract, + .cleaner = &clean_econtract, + .field = name, + .ptr = econtract + }; + + return ret; +} + + +/** + * Parse given JSON object to an age commitmnet + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_age_commitment (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_AgeCommitment *age_commitment = spec->ptr; + json_t *pk; + unsigned int idx; + size_t num; + + (void) cls; + if ( (NULL == root) || + (! json_is_array (root))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + num = json_array_size (root); + if (32 <= num || 0 == num) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + age_commitment->num = num; + age_commitment->keys = + GNUNET_new_array (num, + struct TALER_AgeCommitmentPublicKeyP); + + json_array_foreach (root, idx, pk) { + const char *emsg; + unsigned int eline; + struct GNUNET_JSON_Specification pkspec[] = { + GNUNET_JSON_spec_fixed_auto ( + NULL, + &age_commitment->keys[idx].pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (pk, + pkspec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + }; + + return GNUNET_OK; +} + + +/** + * Cleanup data left from parsing age commitment + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_age_commitment (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_AgeCommitment *age_commitment = spec->ptr; + + (void) cls; + + if (NULL == age_commitment || + NULL == age_commitment->keys) + return; + + age_commitment->num = 0; + GNUNET_free (age_commitment->keys); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_age_commitment (const char *name, + struct TALER_AgeCommitment *age_commitment) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_age_commitment, + .cleaner = &clean_age_commitment, + .field = name, + .ptr = age_commitment + }; + + return ret; +} + + +/** + * Parse given JSON object to denomination public key. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_denom_pub (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_DenominationPublicKey *denom_pub = spec->ptr; + struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub; + const char *cipher; + bool age_mask_missing = false; + struct GNUNET_JSON_Specification dspec[] = { + GNUNET_JSON_spec_string ("cipher", + &cipher), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("age_mask", + &denom_pub->age_mask.bits), + &age_mask_missing), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + (void) cls; + if (GNUNET_OK != + GNUNET_JSON_parse (root, + dspec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (age_mask_missing) + denom_pub->age_mask.bits = 0; + bsign_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey); + bsign_pub->rc = 1; + bsign_pub->cipher = string_to_cipher (cipher); + switch (bsign_pub->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_rsa_public_key ( + "rsa_public_key", + &bsign_pub->details.rsa_public_key), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_free (bsign_pub); + return GNUNET_SYSERR; + } + denom_pub->bsign_pub_key = bsign_pub; + return GNUNET_OK; + } + case GNUNET_CRYPTO_BSA_CS: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed ("cs_public_key", + &bsign_pub->details.cs_public_key, + sizeof (bsign_pub->details.cs_public_key)), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_free (bsign_pub); + return GNUNET_SYSERR; + } + denom_pub->bsign_pub_key = bsign_pub; + return GNUNET_OK; + } + } + GNUNET_break_op (0); + GNUNET_free (bsign_pub); + return GNUNET_SYSERR; +} + + +/** + * Cleanup data left from parsing denomination public key. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_denom_pub (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_DenominationPublicKey *denom_pub = spec->ptr; + + (void) cls; + TALER_denom_pub_free (denom_pub); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_denom_pub (const char *field, + struct TALER_DenominationPublicKey *pk) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_denom_pub, + .cleaner = &clean_denom_pub, + .field = field, + .ptr = pk + }; + + pk->bsign_pub_key = NULL; + return ret; +} + + +/** + * Parse given JSON object partially into a denomination public key. + * + * Depending on the cipher in cls, it parses the corresponding public key type. + * + * @param cls closure, enum GNUNET_CRYPTO_BlindSignatureAlgorithm + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_denom_pub_cipher (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_DenominationPublicKey *denom_pub = spec->ptr; + enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher = + (enum GNUNET_CRYPTO_BlindSignatureAlgorithm) (long) cls; + struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub; + const char *emsg; + unsigned int eline; + + bsign_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey); + bsign_pub->cipher = cipher; + bsign_pub->rc = 1; + switch (cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_rsa_public_key ( + "rsa_pub", + &bsign_pub->details.rsa_public_key), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_free (bsign_pub); + return GNUNET_SYSERR; + } + denom_pub->bsign_pub_key = bsign_pub; + return GNUNET_OK; + } + case GNUNET_CRYPTO_BSA_CS: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed ("cs_pub", + &bsign_pub->details.cs_public_key, + sizeof (bsign_pub->details.cs_public_key)), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_free (bsign_pub); + return GNUNET_SYSERR; + } + denom_pub->bsign_pub_key = bsign_pub; + return GNUNET_OK; + } + } + GNUNET_break_op (0); + GNUNET_free (bsign_pub); + return GNUNET_SYSERR; +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_denom_pub_cipher (const char *field, + enum GNUNET_CRYPTO_BlindSignatureAlgorithm + cipher, + struct TALER_DenominationPublicKey *pk) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_denom_pub_cipher, + .cleaner = &clean_denom_pub, + .field = field, + .cls = (void *) cipher, + .ptr = pk + }; + + return ret; +} + + +/** + * Parse given JSON object to denomination signature. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_denom_sig (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_DenominationSignature *denom_sig = spec->ptr; + struct GNUNET_CRYPTO_UnblindedSignature *unblinded_sig; + const char *cipher; + struct GNUNET_JSON_Specification dspec[] = { + GNUNET_JSON_spec_string ("cipher", + &cipher), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + (void) cls; + if (GNUNET_OK != + GNUNET_JSON_parse (root, + dspec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + unblinded_sig = GNUNET_new (struct GNUNET_CRYPTO_UnblindedSignature); + unblinded_sig->cipher = string_to_cipher (cipher); + unblinded_sig->rc = 1; + switch (unblinded_sig->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_rsa_signature ( + "rsa_signature", + &unblinded_sig->details.rsa_signature), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_free (unblinded_sig); + return GNUNET_SYSERR; + } + denom_sig->unblinded_sig = unblinded_sig; + return GNUNET_OK; + } + case GNUNET_CRYPTO_BSA_CS: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed_auto ("cs_signature_r", + &unblinded_sig->details.cs_signature. + r_point), + GNUNET_JSON_spec_fixed_auto ("cs_signature_s", + &unblinded_sig->details.cs_signature. + s_scalar), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_free (unblinded_sig); + return GNUNET_SYSERR; + } + denom_sig->unblinded_sig = unblinded_sig; + return GNUNET_OK; + } + } + GNUNET_break_op (0); + GNUNET_free (unblinded_sig); + return GNUNET_SYSERR; +} + + +/** + * Cleanup data left from parsing denomination public key. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_denom_sig (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_DenominationSignature *denom_sig = spec->ptr; + + (void) cls; + TALER_denom_sig_free (denom_sig); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_denom_sig (const char *field, + struct TALER_DenominationSignature *sig) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_denom_sig, + .cleaner = &clean_denom_sig, + .field = field, + .ptr = sig + }; + + sig->unblinded_sig = NULL; + return ret; +} + + +/** + * Parse given JSON object to blinded denomination signature. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_blinded_denom_sig (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_BlindedDenominationSignature *denom_sig = spec->ptr; + struct GNUNET_CRYPTO_BlindedSignature *blinded_sig; + const char *cipher; + struct GNUNET_JSON_Specification dspec[] = { + GNUNET_JSON_spec_string ("cipher", + &cipher), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + (void) cls; + if (GNUNET_OK != + GNUNET_JSON_parse (root, + dspec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + blinded_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature); + blinded_sig->cipher = string_to_cipher (cipher); + blinded_sig->rc = 1; + switch (blinded_sig->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_rsa_signature ( + "blinded_rsa_signature", + &blinded_sig->details.blinded_rsa_signature), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_free (blinded_sig); + return GNUNET_SYSERR; + } + denom_sig->blinded_sig = blinded_sig; + return GNUNET_OK; + } + case GNUNET_CRYPTO_BSA_CS: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_uint32 ("b", + &blinded_sig->details.blinded_cs_answer.b), + GNUNET_JSON_spec_fixed_auto ("s", + &blinded_sig->details.blinded_cs_answer. + s_scalar), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_free (blinded_sig); + return GNUNET_SYSERR; + } + denom_sig->blinded_sig = blinded_sig; + return GNUNET_OK; + } + } + GNUNET_break_op (0); + GNUNET_free (blinded_sig); + return GNUNET_SYSERR; +} + + +/** + * Cleanup data left from parsing denomination public key. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_blinded_denom_sig (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_BlindedDenominationSignature *denom_sig = spec->ptr; + + (void) cls; + TALER_blinded_denom_sig_free (denom_sig); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_blinded_denom_sig ( + const char *field, + struct TALER_BlindedDenominationSignature *sig) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_blinded_denom_sig, + .cleaner = &clean_blinded_denom_sig, + .field = field, + .ptr = sig + }; + + sig->blinded_sig = NULL; + return ret; +} + + +/** + * Parse given JSON object to blinded planchet. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_blinded_planchet (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_BlindedPlanchet *blinded_planchet = spec->ptr; + struct GNUNET_CRYPTO_BlindedMessage *blinded_message; + const char *cipher; + struct GNUNET_JSON_Specification dspec[] = { + GNUNET_JSON_spec_string ("cipher", + &cipher), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + + (void) cls; + if (GNUNET_OK != + GNUNET_JSON_parse (root, + dspec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + blinded_message = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage); + blinded_message->rc = 1; + blinded_message->cipher = string_to_cipher (cipher); + switch (blinded_message->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_varsize ( + "rsa_blinded_planchet", + &blinded_message->details.rsa_blinded_message.blinded_msg, + &blinded_message->details.rsa_blinded_message.blinded_msg_size), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_free (blinded_message); + return GNUNET_SYSERR; + } + blinded_planchet->blinded_message = blinded_message; + return GNUNET_OK; + } + case GNUNET_CRYPTO_BSA_CS: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed_auto ( + "cs_nonce", + &blinded_message->details.cs_blinded_message.nonce), + GNUNET_JSON_spec_fixed_auto ( + "cs_blinded_c0", + &blinded_message->details.cs_blinded_message.c[0]), + GNUNET_JSON_spec_fixed_auto ( + "cs_blinded_c1", + &blinded_message->details.cs_blinded_message.c[1]), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_free (blinded_message); + return GNUNET_SYSERR; + } + blinded_planchet->blinded_message = blinded_message; + return GNUNET_OK; + } + } + GNUNET_break_op (0); + GNUNET_free (blinded_message); + return GNUNET_SYSERR; +} + + +/** + * Cleanup data left from parsing blinded planchet. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_blinded_planchet (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_BlindedPlanchet *blinded_planchet = spec->ptr; + + (void) cls; + TALER_blinded_planchet_free (blinded_planchet); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_blinded_planchet (const char *field, + struct TALER_BlindedPlanchet *blinded_planchet) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_blinded_planchet, + .cleaner = &clean_blinded_planchet, + .field = field, + .ptr = blinded_planchet + }; + + blinded_planchet->blinded_message = NULL; + return ret; +} + + +/** + * Parse given JSON object to exchange withdraw values (/csr). + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_exchange_withdraw_values (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_ExchangeWithdrawValues *ewv = spec->ptr; + struct GNUNET_CRYPTO_BlindingInputValues *bi; + const char *cipher; + struct GNUNET_JSON_Specification dspec[] = { + GNUNET_JSON_spec_string ("cipher", + &cipher), + GNUNET_JSON_spec_end () + }; + const char *emsg; + unsigned int eline; + enum GNUNET_CRYPTO_BlindSignatureAlgorithm ci; + + (void) cls; + if (GNUNET_OK != + GNUNET_JSON_parse (root, + dspec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + ci = string_to_cipher (cipher); + switch (ci) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + ewv->blinding_inputs = TALER_denom_ewv_rsa_singleton ()->blinding_inputs; + return GNUNET_OK; + case GNUNET_CRYPTO_BSA_CS: + bi = GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues); + bi->cipher = GNUNET_CRYPTO_BSA_CS; + bi->rc = 1; + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed ( + "r_pub_0", + &bi->details.cs_values.r_pub[0], + sizeof (struct GNUNET_CRYPTO_CsRPublic)), + GNUNET_JSON_spec_fixed ( + "r_pub_1", + &bi->details.cs_values.r_pub[1], + sizeof (struct GNUNET_CRYPTO_CsRPublic)), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + GNUNET_free (bi); + return GNUNET_SYSERR; + } + ewv->blinding_inputs = bi; + return GNUNET_OK; + } + } + GNUNET_break_op (0); + return GNUNET_SYSERR; +} + + +/** + * Cleanup data left from parsing withdraw values + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_exchange_withdraw_values ( + void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_ExchangeWithdrawValues *ewv = spec->ptr; + + (void) cls; + TALER_denom_ewv_free (ewv); +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_exchange_withdraw_values ( + const char *field, + struct TALER_ExchangeWithdrawValues *ewv) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_exchange_withdraw_values, + .cleaner = &clean_exchange_withdraw_values, + .field = field, + .ptr = ewv + }; + + ewv->blinding_inputs = NULL; + return ret; +} + + +/** + * Closure for #parse_i18n_string. + */ +struct I18nContext +{ + /** + * Language pattern to match. + */ + char *lp; + + /** + * Name of the field to match. + */ + const char *field; +}; + + +/** + * Parse given JSON object to internationalized string. + * + * @param cls closure, our `struct I18nContext *` + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_i18n_string (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct I18nContext *ctx = cls; + json_t *i18n; + json_t *val; + + { + char *i18nf; + + GNUNET_asprintf (&i18nf, + "%s_i18n", + ctx->field); + i18n = json_object_get (root, + i18nf); + GNUNET_free (i18nf); + } + + val = json_object_get (root, + ctx->field); + if ( (NULL != i18n) && + (NULL != ctx->lp) ) + { + double best = 0.0; + json_t *pos; + const char *lang; + + json_object_foreach (i18n, lang, pos) + { + double score; + + score = TALER_language_matches (ctx->lp, + lang); + if (score > best) + { + best = score; + val = pos; + } + } + } + + { + const char *str; + + str = json_string_value (val); + *(const char **) spec->ptr = str; + } + return GNUNET_OK; +} + + +/** + * Function called to clean up data from earlier parsing. + * + * @param cls closure + * @param spec our specification entry with data to clean. + */ +static void +i18n_cleaner (void *cls, + struct GNUNET_JSON_Specification *spec) +{ + struct I18nContext *ctx = cls; + + (void) spec; + if (NULL != ctx) + { + GNUNET_free (ctx->lp); + GNUNET_free (ctx); + } +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_i18n_string (const char *name, + const char *language_pattern, + const char **strptr) +{ + struct I18nContext *ctx = GNUNET_new (struct I18nContext); + struct GNUNET_JSON_Specification ret = { + .parser = &parse_i18n_string, + .cleaner = &i18n_cleaner, + .cls = ctx, + .field = NULL, /* we want the main object */ + .ptr = strptr, + .ptr_size = 0, + .size_ptr = NULL + }; + + ctx->lp = (NULL != language_pattern) + ? GNUNET_strdup (language_pattern) + : NULL; + ctx->field = name; + *strptr = NULL; + return ret; +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_i18n_str (const char *name, + const char **strptr) +{ + const char *lang = getenv ("LANG"); + char *dot; + char *l; + struct GNUNET_JSON_Specification ret; + + if (NULL != lang) + { + dot = strchr (lang, + '.'); + if (NULL == dot) + l = GNUNET_strdup (lang); + else + l = GNUNET_strndup (lang, + dot - lang); + } + else + { + l = NULL; + } + ret = TALER_JSON_spec_i18n_string (name, + l, + strptr); + GNUNET_free (l); + return ret; +} + + +/** + * Parse given JSON object with Taler error code. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_ec (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + enum TALER_ErrorCode *ec = spec->ptr; + json_int_t num; + + (void) cls; + if (! json_is_integer (root)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + num = json_integer_value (root); + if (num < 0) + { + GNUNET_break_op (0); + *ec = TALER_EC_INVALID; + return GNUNET_SYSERR; + } + *ec = (enum TALER_ErrorCode) num; + return GNUNET_OK; +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_ec (const char *field, + enum TALER_ErrorCode *ec) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_ec, + .field = field, + .ptr = ec + }; + + *ec = TALER_EC_NONE; + return ret; +} + + +/** + * Parse given JSON object to web URL. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_web_url (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + const char *str; + + (void) cls; + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (! TALER_is_web_url (str)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *(const char **) spec->ptr = str; + return GNUNET_OK; +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_web_url (const char *field, + const char **url) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_web_url, + .field = field, + .ptr = url + }; + + *url = NULL; + return ret; +} + + +/** + * Parse given JSON object to payto:// URI. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_payto_uri (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + const char *str; + char *err; + + (void) cls; + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + err = TALER_payto_validate (str); + if (NULL != err) + { + GNUNET_break_op (0); + GNUNET_free (err); + return GNUNET_SYSERR; + } + *(const char **) spec->ptr = str; + return GNUNET_OK; +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_payto_uri (const char *field, + const char **payto_uri) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_payto_uri, + .field = field, + .ptr = payto_uri + }; + + *payto_uri = NULL; + return ret; +} + + +/** + * Parse given JSON object with protocol version. + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_protocol_version (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct TALER_JSON_ProtocolVersion *pv = spec->ptr; + const char *ver; + char dummy; + + (void) cls; + if (! json_is_string (root)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + ver = json_string_value (root); + if (3 != sscanf (ver, + "%u:%u:%u%c", + &pv->current, + &pv->revision, + &pv->age, + &dummy)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +struct GNUNET_JSON_Specification +TALER_JSON_spec_version (const char *field, + struct TALER_JSON_ProtocolVersion *ver) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_protocol_version, + .field = field, + .ptr = ver + }; + + return ret; +} + + +/* end of json/json_helper.c */ diff --git a/src/json/json_pack.c b/src/json/json_pack.c @@ -0,0 +1,324 @@ +/* + This file is part of TALER + Copyright (C) 2024 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 json/json_pack.c + * @brief helper functions for JSON object packing + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler/taler_util.h" +#include "taler/taler_json_lib.h" + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_time_abs_human (const char *name, + struct GNUNET_TIME_Absolute at) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + .object = json_string ( + GNUNET_STRINGS_absolute_time_to_string (at)) + }; + + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_econtract ( + const char *name, + const struct TALER_EncryptedContract *econtract) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + + if (NULL == econtract) + return ps; + ps.object + = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_varsize ("econtract", + econtract->econtract, + econtract->econtract_size), + GNUNET_JSON_pack_data_auto ("econtract_sig", + &econtract->econtract_sig), + GNUNET_JSON_pack_data_auto ("contract_pub", + &econtract->contract_pub)); + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_age_commitment ( + const char *name, + const struct TALER_AgeCommitment *age_commitment) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + json_t *keys; + + if (NULL == age_commitment || + 0 == age_commitment->num) + return ps; + + GNUNET_assert (NULL != + (keys = json_array ())); + + for (size_t i = 0; + i < age_commitment->num; + i++) + { + json_t *val; + val = GNUNET_JSON_from_data (&age_commitment->keys[i], + sizeof(age_commitment->keys[i])); + GNUNET_assert (NULL != val); + GNUNET_assert (0 == + json_array_append_new (keys, val)); + } + + ps.object = keys; + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_denom_pub ( + const char *name, + const struct TALER_DenominationPublicKey *pk) +{ + const struct GNUNET_CRYPTO_BlindSignPublicKey *bsp; + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + + if (NULL == pk) + return ps; + bsp = pk->bsign_pub_key; + switch (bsp->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + ps.object + = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "RSA"), + GNUNET_JSON_pack_uint64 ("age_mask", + pk->age_mask.bits), + GNUNET_JSON_pack_rsa_public_key ("rsa_public_key", + bsp->details.rsa_public_key)); + return ps; + case GNUNET_CRYPTO_BSA_CS: + ps.object + = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "CS"), + GNUNET_JSON_pack_uint64 ("age_mask", + pk->age_mask.bits), + GNUNET_JSON_pack_data_varsize ("cs_public_key", + &bsp->details.cs_public_key, + sizeof (bsp->details.cs_public_key))); + return ps; + } + GNUNET_assert (0); + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_denom_sig ( + const char *name, + const struct TALER_DenominationSignature *sig) +{ + const struct GNUNET_CRYPTO_UnblindedSignature *bs; + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + + if (NULL == sig) + return ps; + bs = sig->unblinded_sig; + switch (bs->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "RSA"), + GNUNET_JSON_pack_rsa_signature ("rsa_signature", + bs->details.rsa_signature)); + return ps; + case GNUNET_CRYPTO_BSA_CS: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "CS"), + GNUNET_JSON_pack_data_auto ("cs_signature_r", + &bs->details.cs_signature.r_point), + GNUNET_JSON_pack_data_auto ("cs_signature_s", + &bs->details.cs_signature.s_scalar)); + return ps; + } + GNUNET_assert (0); + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_exchange_withdraw_values ( + const char *name, + const struct TALER_ExchangeWithdrawValues *ewv) +{ + const struct GNUNET_CRYPTO_BlindingInputValues *biv; + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + + if (NULL == ewv) + return ps; + biv = ewv->blinding_inputs; + switch (biv->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "RSA")); + return ps; + case GNUNET_CRYPTO_BSA_CS: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "CS"), + GNUNET_JSON_pack_data_varsize ( + "r_pub_0", + &biv->details.cs_values.r_pub[0], + sizeof(struct GNUNET_CRYPTO_CsRPublic)), + GNUNET_JSON_pack_data_varsize ( + "r_pub_1", + &biv->details.cs_values.r_pub[1], + sizeof(struct GNUNET_CRYPTO_CsRPublic)) + ); + return ps; + } + GNUNET_assert (0); + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_blinded_denom_sig ( + const char *name, + const struct TALER_BlindedDenominationSignature *sig) +{ + const struct GNUNET_CRYPTO_BlindedSignature *bs; + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + + if (NULL == sig) + return ps; + bs = sig->blinded_sig; + switch (bs->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "RSA"), + GNUNET_JSON_pack_rsa_signature ("blinded_rsa_signature", + bs->details.blinded_rsa_signature)); + return ps; + case GNUNET_CRYPTO_BSA_CS: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "CS"), + GNUNET_JSON_pack_uint64 ("b", + bs->details.blinded_cs_answer.b), + GNUNET_JSON_pack_data_auto ("s", + &bs->details.blinded_cs_answer.s_scalar)); + return ps; + } + GNUNET_assert (0); + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_blinded_planchet ( + const char *name, + const struct TALER_BlindedPlanchet *blinded_planchet) +{ + const struct GNUNET_CRYPTO_BlindedMessage *bm; + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + }; + + if (NULL == blinded_planchet) + return ps; + bm = blinded_planchet->blinded_message; + switch (bm->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "RSA"), + GNUNET_JSON_pack_data_varsize ( + "rsa_blinded_planchet", + bm->details.rsa_blinded_message.blinded_msg, + bm->details.rsa_blinded_message.blinded_msg_size)); + return ps; + case GNUNET_CRYPTO_BSA_CS: + ps.object = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "CS"), + GNUNET_JSON_pack_data_auto ( + "cs_nonce", + &bm->details.cs_blinded_message.nonce), + GNUNET_JSON_pack_data_auto ( + "cs_blinded_c0", + &bm->details.cs_blinded_message.c[0]), + GNUNET_JSON_pack_data_auto ( + "cs_blinded_c1", + &bm->details.cs_blinded_message.c[1])); + return ps; + } + GNUNET_assert (0); + return ps; +} + + +struct GNUNET_JSON_PackSpec +TALER_JSON_pack_amount (const char *name, + const struct TALER_Amount *amount) +{ + struct GNUNET_JSON_PackSpec ps = { + .field_name = name, + .object = (NULL != amount) + ? TALER_JSON_from_amount (amount) + : NULL + }; + + return ps; +} + + +/* End of json/json_pack.c */ diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am @@ -13,8 +13,8 @@ endif # Libraries -lib_LTLIBRARIES = \ - libtalerdonau.la +#lib_LTLIBRARIES = \ +# libtalerdonau.la libdonau_la_LDFLAGS = \ -version-info 5:0:0 \ diff --git a/src/lib/donau_api_handle.c b/src/lib/donau_api_handle.c @@ -384,9 +384,9 @@ decode_keys_json (const json_t *resp_obj, bool found = false; struct GNUNET_JSON_Specification kspec[] = { - TALER_JSON_spec_denom_pub_cipher (NULL, // TODO write new JSON function - group.cipher, - &dk->key), + DONAU_JSON_spec_donation_unit_pub_cipher (NULL, + group.cipher, + &dk->key), GNUNET_JSON_spec_end () }; diff --git a/src/util/donau_signatures.c b/src/util/donau_signatures.c @@ -35,7 +35,7 @@ struct DONAU_DonationStatementConfirmationPS { /** * Purpose must be #DONAU_SIGNATURE_DONAU_DONATION_STATEMENT. Signed - * by a `struct DONAU_EddsaPublicKeyP` using EdDSA. + * by a `struct DONAU_DonauPublicKeyP` using EdDSA. */ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;