donau

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

commit 17a70bd391732802e13e5639e387048fa869fff2
parent 516adf7e3804025ee58072e78aa733afcf5f99a1
Author: Matyja Lukas Adam <lukas.matyja@students.bfh.ch>
Date:   Tue, 14 May 2024 20:20:24 +0200

[testing][lib] add cs client & tests

Diffstat:
Msrc/donau/donau-httpd.c | 2+-
Msrc/donau/donau-httpd_csr.c | 4++--
Msrc/donau/donau-httpd_csr.h | 4++--
Msrc/include/donau_service.h | 14+++++++-------
Msrc/include/donau_testing_lib.h | 2++
Msrc/lib/Makefile.am | 3++-
Asrc/lib/donau_api_csr_post.c | 226+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/testing/test_donau_api.c | 15++++++---------
Msrc/testing/testing_api_cmd_issue_receipts.c | 209+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
9 files changed, 421 insertions(+), 58 deletions(-)

diff --git a/src/donau/donau-httpd.c b/src/donau/donau-httpd.c @@ -54,7 +54,7 @@ * Above what request latency do we start to log? */ #define WARN_LATENCY GNUNET_TIME_relative_multiply ( \ - GNUNET_TIME_UNIT_MILLISECONDS, 500) + GNUNET_TIME_UNIT_MILLISECONDS, 500) /** * Are clients allowed to request /keys for times other than the diff --git a/src/donau/donau-httpd_csr.c b/src/donau/donau-httpd_csr.c @@ -43,8 +43,8 @@ MHD_RESULT DH_handler_csr_issue (struct DH_RequestContext *rc, - const json_t *root, - const char *const args[]) + const json_t *root, + const char *const args[]) { struct GNUNET_CRYPTO_CsSessionNonce nonce; struct TALER_DenominationHashP denom_pub_hash; diff --git a/src/donau/donau-httpd_csr.h b/src/donau/donau-httpd_csr.h @@ -37,7 +37,7 @@ */ MHD_RESULT DH_handler_csr_issue (struct DH_RequestContext *rc, - const json_t *root, - const char *const args[]); + const json_t *root, + const char *const args[]); #endif diff --git a/src/include/donau_service.h b/src/include/donau_service.h @@ -751,13 +751,13 @@ typedef void * In this case, the callback is not called. */ struct DONAU_CsRBatchIssueHandle * -DONAU_csr_batch_issue ( - struct GNUNET_CURL_Context *curl_ctx, - const char *donau_url, +DONAU_csr_issue ( + struct GNUNET_CURL_Context *ctx, + const char *url, const struct DONAU_DonationUnitPublicKey *pk, - const struct GNUNET_CRYPTO_CsBlindingNonce nonce, - DONAU_CsRBatchIssueCallback res_cb, - void *res_cb_cls); + const struct GNUNET_CRYPTO_CsSessionNonce *nonce, + DONAU_CsRBatchIssueCallback cb, + void *cb_cls); /** @@ -768,7 +768,7 @@ DONAU_csr_batch_issue ( * @param csrh the batch-issue handle */ void -DONAU_csr_batch_issue_cancel ( +DONAU_csr_cancel ( struct DONAU_CsRBatchIssueHandle *csrh); diff --git a/src/include/donau_testing_lib.h b/src/include/donau_testing_lib.h @@ -117,6 +117,7 @@ TALER_TESTING_cmd_charities_get (const char *label, * * @param label the command label. * @param charity_reference reference for traits + * @param uses_cs true for cs and false for rsa. * @param year current year (mostly) * @param donor_tax_id tax id of the donor * @param salt for tax id hash @@ -126,6 +127,7 @@ TALER_TESTING_cmd_charities_get (const char *label, struct TALER_TESTING_Command TALER_TESTING_cmd_issue_receipts (const char *label, const char *charity_reference, + const bool uses_cs, const uint64_t year, const char *donor_tax_id, const char *salt, diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am @@ -27,7 +27,8 @@ libdonau_la_SOURCES = \ donau_api_charities_get.c \ donau_api_curl_defaults.c donau_api_curl_defaults.h \ donau_api_batch_issue_receipts.c \ - donau_api_batch_submit_receipts.c + donau_api_batch_submit_receipts.c \ + donau_api_csr_post.c ## maybe need libtalercurl libdonau_la_LIBADD = \ diff --git a/src/lib/donau_api_csr_post.c b/src/lib/donau_api_csr_post.c @@ -0,0 +1,226 @@ +/* + 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 lib/donau_api_csr_post.c + * @brief Implementation of the "handle" component of the donau's HTTP API + * @author Lukas Matyja + */ +#include <gnunet/gnunet_curl_lib.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> +#include "donau_service.h" +#include "donau_api_curl_defaults.h" +#include "donau_json_lib.h" + + +/** + * Handle for a POST /csr-issue request. + */ +struct DONAU_CsRBatchIssueHandle +{ + /** + * The url for the /csr-issue request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Entry for this request with the `struct GNUNET_CURL_Context`. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + DONAU_CsRBatchIssueCallback cb; + + /** + * Closure to pass to @e cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + +/** + * Function called when we're done processing the + * HTTP POST /csr-issue request. + * + * @param cls the `struct KeysRequest` + * @param response_code HTTP response code, 0 on error + * @param resp_obj parsed JSON result, NULL on error + */ +static void +handle_csr_issue_post_finished (void *cls, + long response_code, + const void *resp_obj) +{ + struct DONAU_CsRBatchIssueHandle *csrh = cls; + const json_t *j = resp_obj; + + struct DONAU_CsRBatchIssueResponse csrresp = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + csrh->job = NULL; + switch (response_code) + { + case MHD_HTTP_CREATED: + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_exchange_withdraw_values ( // TODO: method for GNUNET + "ewv", + (struct TALER_ExchangeWithdrawValues *) &csrresp.details.ok.alg_values), + GNUNET_JSON_spec_end () + }; + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not parse response from csr POST\n"); + GNUNET_break_op (0); + } + csrh->cb (csrh->cb_cls, + &csrresp); + break; + // Donation unit was revoked. + case MHD_HTTP_GONE: + csrresp.hr.ec = TALER_JSON_get_error_code (j); + csrresp.hr.hint = TALER_JSON_get_error_hint (j); + break; + // Donation unit or endpoint not found. + case MHD_HTTP_NOT_FOUND: + csrresp.hr.ec = TALER_JSON_get_error_code (j); + csrresp.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + csrresp.hr.ec = TALER_JSON_get_error_code (j); + csrresp.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for POST %s\n", + (unsigned int) response_code, + (int) csrresp.hr.ec, + csrh->url); + break; + } + if (NULL != csrh->cb) + { + csrh->cb (csrh->cb_cls, + &csrresp); + csrh->cb = NULL; + } + DONAU_csr_cancel (csrh); +} + + +struct DONAU_CsRBatchIssueHandle * +DONAU_csr_issue ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct DONAU_DonationUnitPublicKey *pk, + const struct GNUNET_CRYPTO_CsSessionNonce *nonce, + DONAU_CsRBatchIssueCallback cb, + void *cb_cls) +{ + struct DONAU_CsRBatchIssueHandle *csrh; + CURL *eh; + json_t *body; + + struct DONAU_DonationUnitHashP h_donation_unit_pub; + DONAU_donation_unit_pub_hash (pk, + &h_donation_unit_pub); + + TALER_LOG_DEBUG ("Connecting to the donau (%s)\n", + url); + csrh = GNUNET_new (struct DONAU_CsRBatchIssueHandle); + csrh->url = GNUNET_strdup (url); + csrh->cb = cb; + csrh->cb_cls = cb_cls; + csrh->ctx = ctx; + csrh->url = TALER_url_join (url, + "charities", + NULL); + if (NULL == csrh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct requested URL.\n"); + GNUNET_free (csrh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Request CS R with URL `%s'.\n", + csrh->url); + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_varsize ("nonce", + nonce, + sizeof(*nonce)), + GNUNET_JSON_pack_data_varsize ("donation_unit_pub_hash", + &h_donation_unit_pub, + sizeof(h_donation_unit_pub))); + eh = DONAU_curl_easy_get_ (csrh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&csrh->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (csrh->url); + return NULL; + } + json_decref (body); + csrh->job = GNUNET_CURL_job_add2 (ctx, + eh, + csrh->post_ctx.headers, + &handle_csr_issue_post_finished, + csrh); + return csrh; +} + + +void +DONAU_csr_cancel ( + struct DONAU_CsRBatchIssueHandle *csrh) +{ + if (NULL != csrh->job) + { + GNUNET_CURL_job_cancel (csrh->job); + csrh->job = NULL; + } + TALER_curl_easy_post_finished (&csrh->post_ctx); + GNUNET_free (csrh->url); + GNUNET_free (csrh); +} diff --git a/src/testing/test_donau_api.c b/src/testing/test_donau_api.c @@ -45,11 +45,7 @@ static char *config_file; static struct TALER_TESTING_Credentials cred; /** - * Some tests behave differently when using CS as we cannot - * reuse the coin private key for different denominations - * due to the derivation of it with the /csr values. Hence - * some tests behave differently in CS mode, hence this - * flag. + * Issue receipts tests behave differently when using CS. */ static bool uses_cs; @@ -94,14 +90,15 @@ run (void *cls, // FIXME TALER_TESTING_cmd_issue_receipts ("issue-receipts", "post-charity", + uses_cs, 2024, "7560001010000", // tax id "1234", // salt for tax id hash MHD_HTTP_CREATED), - TALER_TESTING_cmd_submit_receipts ("submit-receipts", - "issue-receipts", // cmd trait reference - 2024, - MHD_HTTP_OK), +// TALER_TESTING_cmd_submit_receipts ("submit-receipts", +// "issue-receipts", // cmd trait reference +// 2024, +// MHD_HTTP_OK), TALER_TESTING_cmd_charity_delete ("delete-charity", "post-charity", // cmd trait reference &bearer, diff --git a/src/testing/testing_api_cmd_issue_receipts.c b/src/testing/testing_api_cmd_issue_receipts.c @@ -34,7 +34,7 @@ struct StatusState { /** - * Handle to the "charity status" operation. + * Handle to the "batch issue receipt status" operation. */ struct DONAU_BatchIssueReceiptHandle *birh; @@ -44,16 +44,15 @@ struct StatusState const char *charity_reference; /** - * Private key of the charity, for signature. - */ - struct DONAU_CharityPrivateKeyP charity_priv; - - /** * Expected HTTP response code. */ unsigned int expected_response_code; /** + */ + bool uses_cs; + + /** * Interpreter state. */ struct TALER_TESTING_Interpreter *is; @@ -69,6 +68,11 @@ struct StatusState unsigned long long year; /** + * Private key of the charity, for signature. + */ + struct DONAU_CharityPrivateKeyP charity_priv; + + /** * number of budi key pair. */ size_t num_bkp; @@ -107,6 +111,35 @@ struct StatusState * Array of hashed udis. */ struct DONAU_UniqueDonorIdentifierHashP *h_udis; + + /** + * Number of pending CS requests. + */ + size_t cs_pending; +}; + + +struct CSR_Data +{ + /** + * Handle to the "batch issue receipt status" operation. + */ + struct DONAU_CsRBatchIssueHandle *csr_handle; + + /** + * CS-Nonce + */ + union GNUNET_CRYPTO_BlindSessionNonce nonce; + + /** + * batch issue receipt status state + */ + struct StatusState *ss; + + /** + * array position in batch issue receipt request (first position is zero) + */ + size_t position; }; @@ -157,6 +190,92 @@ issue_receipts_status_cb (void *cls, /** + * Runs phase two, the actual issue receipts operation. + * Started once the preparation for CS-donation-units is + * done. + * @param cls closure. + */ +static void +phase_two (void *cls) +{ + struct StatusState *ss = cls; + const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps = ss->bkps; + ss->birh = DONAU_charity_issue_receipt ( + TALER_TESTING_interpreter_get_context (ss->is), + TALER_TESTING_get_donau_url (ss->is), + &ss->charity_priv, + ss->charity_id, + ss->year, + ss->num_bkp, + bkps, + &issue_receipts_status_cb, + ss); +} + + +/** + * Function called when stage 1 of CS issue is finished (request r_pub's) + * + * @param cls the `struct CSR_Data *` + * @param csrresp replies from the /csr-issue request + */ +static void +cs_stage_two_callback ( + void *cls, + const struct DONAU_CsRBatchIssueResponse *csrresp) +{ + struct CSR_Data *csr_data = cls; + csr_data->csr_handle = NULL; + if (csrresp->hr.http_status != MHD_HTTP_CREATED) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected HTTP response code: %d in %s:%u\n", + csrresp->hr.http_status, + __FILE__, + __LINE__); + json_dumpf (csrresp->hr.reply, + stderr, + 0); + TALER_TESTING_interpreter_fail (csr_data->ss->is); + return; + } + + struct DONAU_DonationUnitPublicKey *cs_pk = &csr_data->ss->keys->donation_unit_keys[csr_data->position].key; + const struct DONAU_BatchIssueValues *alg_values = csr_data->ss->alg_values[csr_data->position]; + struct DONAU_BudiMasterSecretP ps; + struct DONAU_BlindedUniqueDonorIdentifier *blinded_udi = &csr_data->ss->bkps[csr_data->position].blinded_udi; + struct DONAU_UniqueDonorIdentifierHashP *udi_hash = csr_data->ss->h_udis; + union GNUNET_CRYPTO_BlindingSecretP *blinding_secret = &csr_data->ss->blinding_secrets[csr_data->position]; + struct DONAU_UniqueDonorIdentifierNonce *udi_nonce = &csr_data->ss->receipts[csr_data->position].nonce; + + GNUNET_assert (GNUNET_CRYPTO_BSA_CS == cs_pk->bsign_pub_key->cipher); + GNUNET_assert (NULL == alg_values); + // TODO: write GNUNET method + TALER_denom_ewv_copy ((struct TALER_ExchangeWithdrawValues *) alg_values, + (struct TALER_ExchangeWithdrawValues *) &csrresp->details.ok.alg_values); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, + &ps, + sizeof (ps)); + DONAU_budi_secret_create (&ps, + alg_values, + blinding_secret); + GNUNET_assert (GNUNET_OK == + DONAU_donation_unit_blind ( + cs_pk, + blinding_secret, + &csr_data->nonce, /* nonce only needed for cs */ + udi_nonce, + csr_data->ss->h_donor_tax_id, + alg_values, + udi_hash, + blinded_udi)); + csr_data->ss->cs_pending--; + if (0 == csr_data->ss->cs_pending) + phase_two (csr_data->ss); + GNUNET_free(csr_data); +} + +/** * Run the command. * * @param cls closure. @@ -227,6 +346,8 @@ status_run (void *cls, GNUNET_new_array (ss->num_bkp, struct DONAU_UniqueDonorIdentifierHashP); for (size_t cnt = 0; cnt < ss->num_bkp; cnt++) { + DONAU_donation_unit_pub_hash (&ss->keys->donation_unit_keys[0].key, + &ss->bkps[cnt].h_donation_unit_pub); struct DONAU_UniqueDonorIdentifierNonce *udi_nonce = &ss->receipts[cnt].nonce; struct DONAU_BudiMasterSecretP ps; @@ -234,43 +355,57 @@ status_run (void *cls, struct DONAU_BlindedUniqueDonorIdentifier *blinded_udi = &ss->bkps[cnt].blinded_udi; struct DONAU_UniqueDonorIdentifierHashP *udi_hash = ss->h_udis; - alg_values = DONAU_donation_unit_ewv_rsa_singleton (); // FIXME: support cs and rsa - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, &ps, sizeof (ps)); - DONAU_budi_secret_create (&ps, - alg_values, - &ss->blinding_secrets[cnt]); GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, udi_nonce, sizeof (*udi_nonce)); - GNUNET_assert (GNUNET_OK == - DONAU_donation_unit_blind ( - &ss->keys->donation_unit_keys[0].key, - &ss->blinding_secrets[cnt], - NULL, /* no cs-nonce needed for rsa */ - udi_nonce, - ss->h_donor_tax_id, - alg_values, - udi_hash, - blinded_udi)); - ss->alg_values[cnt] = alg_values; - DONAU_donation_unit_pub_hash (&ss->keys->donation_unit_keys[0].key, - &ss->bkps[cnt].h_donation_unit_pub); + switch (ss->keys->donation_unit_keys[0].key.bsign_pub_key->cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + alg_values = DONAU_donation_unit_ewv_rsa_singleton (); + DONAU_budi_secret_create (&ps, + alg_values, + &ss->blinding_secrets[cnt]); + GNUNET_assert (GNUNET_OK == + DONAU_donation_unit_blind ( + &ss->keys->donation_unit_keys[0].key, + &ss->blinding_secrets[cnt], + NULL, /* no cs-nonce needed for rsa */ + udi_nonce, + ss->h_donor_tax_id, + alg_values, + udi_hash, + blinded_udi)); + ss->alg_values[cnt] = alg_values; + break; + case GNUNET_CRYPTO_BSA_CS: + struct CSR_Data *csr_data = GNUNET_new (struct CSR_Data); + TALER_cs_withdraw_nonce_derive ( // TODO: write new method + (struct TALER_PlanchetMasterSecretP *) &ps, + &csr_data->nonce.cs_nonce); + csr_data->ss = ss; + csr_data->position = cnt; + csr_data->csr_handle = DONAU_csr_issue ( + TALER_TESTING_interpreter_get_context (is), + TALER_TESTING_get_donau_url (is), + &ss->keys->donation_unit_keys[0].key, + &csr_data->nonce.cs_nonce, + &cs_stage_two_callback, + csr_data); + if (NULL == csr_data->csr_handle) + { + GNUNET_break (0); + } + ss->cs_pending++; + break; + default: + GNUNET_break (0); + } } - const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps = ss->bkps; - ss->birh = DONAU_charity_issue_receipt ( - TALER_TESTING_interpreter_get_context (is), - TALER_TESTING_get_donau_url (is), - &ss->charity_priv, - ss->charity_id, - ss->year, - ss->num_bkp, - bkps, - &issue_receipts_status_cb, - ss); - + if (0 == ss->cs_pending) + phase_two (ss); } @@ -345,6 +480,7 @@ issue_receipts_traits (void *cls, struct TALER_TESTING_Command TALER_TESTING_cmd_issue_receipts (const char *label, const char *charity_reference, + const bool uses_cs, const uint64_t year, const char *donor_tax_id, const char *salt, @@ -358,6 +494,7 @@ TALER_TESTING_cmd_issue_receipts (const char *label, ss->charity_reference = charity_reference; ss->expected_response_code = expected_response_code; ss->num_bkp = 3; + ss->uses_cs = uses_cs; struct DONAU_HashDonorTaxId h_donor_tax_id; struct GNUNET_HashContext *hash_context; hash_context = GNUNET_CRYPTO_hash_context_start ();