From 82405b0ce5593b30a1b0ee1a1995f2214a71751c Mon Sep 17 00:00:00 2001 From: Lucien Heuzeveldt Date: Thu, 6 Jan 2022 15:55:50 +0100 Subject: implement CS key handling and csr endpoint --- src/exchange-tools/taler-exchange-offline.c | 103 +++++++-- src/exchange/Makefile.am | 1 + src/exchange/taler-exchange-httpd.c | 8 + src/exchange/taler-exchange-httpd_csr.c | 178 +++++++++++++++ src/exchange/taler-exchange-httpd_csr.h | 43 ++++ src/exchange/taler-exchange-httpd_keys.c | 239 +++++++++++++++++++-- src/exchange/taler-exchange-httpd_keys.h | 17 ++ src/include/taler_exchange_service.h | 18 +- src/include/taler_json_lib.h | 12 -- src/include/taler_testing_lib.h | 22 +- src/json/json_helper.c | 22 +- src/json/json_pack.c | 13 ++ src/lib/exchange_api_csr.c | 71 +++--- src/lib/exchange_api_management_get_keys.c | 22 ++ src/lib/exchange_api_withdraw.c | 87 ++++++-- src/pq/pq_query_helper.c | 10 +- src/pq/pq_result_helper.c | 11 +- src/testing/.gitignore | 3 + src/testing/test_auditor_api.conf | 4 + src/testing/test_exchange_api.c | 57 +++++ .../test_exchange_api_keys_cherry_picking.conf | 8 + src/testing/testing_api_cmd_refresh.c | 6 +- src/testing/testing_api_cmd_withdraw.c | 59 ++++- src/testing/testing_api_helpers_exchange.c | 54 ++++- 24 files changed, 949 insertions(+), 119 deletions(-) create mode 100644 src/exchange/taler-exchange-httpd_csr.c create mode 100644 src/exchange/taler-exchange-httpd_csr.h (limited to 'src') diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index 6ad345ebf..143a7f265 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -2531,10 +2531,10 @@ do_download (char *const *args) * #GNUNET_SYSERR if keys changed from what we remember or other error */ static int -tofu_check (const struct TALER_SecurityModulePublicKeyP secm[2]) +tofu_check (const struct TALER_SecurityModulePublicKeyP secm[3]) { char *fn; - struct TALER_SecurityModulePublicKeyP old[2]; + struct TALER_SecurityModulePublicKeyP old[3]; ssize_t ret; if (GNUNET_OK != @@ -2608,7 +2608,7 @@ tofu_check (const struct TALER_SecurityModulePublicKeyP secm[2]) GNUNET_free (key); if (0 != GNUNET_memcmp (&k, - &secm[1])) + &secm[2])) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "ESIGN security module key does not match SECM_ESIGN_PUBKEY in configuration\n"); @@ -2646,6 +2646,37 @@ tofu_check (const struct TALER_SecurityModulePublicKeyP secm[2]) return GNUNET_SYSERR; } } + if (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (kcfg, + "exchange-offline", + "SECM_DENOM_CS_PUBKEY", + &key)) + { + struct TALER_SecurityModulePublicKeyP k; + + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (key, + strlen (key), + &k, + sizeof (k))) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "exchange-offline", + "SECM_DENOM_CS_PUBKEY", + "key malformed"); + GNUNET_free (key); + return GNUNET_SYSERR; + } + GNUNET_free (key); + if (0 != + GNUNET_memcmp (&k, + &secm[1])) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "DENOM security module key does not match SECM_DENOM_CS_PUBKEY in configuration\n"); + return GNUNET_SYSERR; + } + } } if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (fn)) @@ -2766,11 +2797,13 @@ show_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub, * Output @a denomkeys for human consumption. * * @param secm_pub security module public key used to sign the denominations + * element 0: RSA + * element 1: CS * @param denomkeys keys to output * @return #GNUNET_OK on success */ static int -show_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub, +show_denomkeys (const struct TALER_SecurityModulePublicKeyP secm_pub[2], const json_t *denomkeys) { size_t index; @@ -2863,10 +2896,24 @@ show_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub, section_name, stamp_start, duration, - secm_pub, + &secm_pub[0], &secm_sig); } break; + case TALER_DENOMINATION_CS: + { + struct TALER_CsPubHashP h_cs; + + TALER_cs_pub_hash (&denom_pub.details.cs_public_key, + &h_cs); + ok = TALER_exchange_secmod_cs_verify (&h_cs, + section_name, + stamp_start, + duration, + &secm_pub[1], + &secm_sig); + } + break; default: GNUNET_break (0); ok = GNUNET_SYSERR; @@ -3018,7 +3065,7 @@ do_show (char *const *args) json_t *denomkeys; json_t *signkeys; struct TALER_MasterPublicKeyP mpub; - struct TALER_SecurityModulePublicKeyP secm[2]; + struct TALER_SecurityModulePublicKeyP secm[3]; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_json ("future_denoms", &denomkeys), @@ -3028,8 +3075,10 @@ do_show (char *const *args) &mpub), GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key", &secm[0]), - GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key", + GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key", &secm[1]), + GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key", + &secm[2]), GNUNET_JSON_spec_end () }; @@ -3079,7 +3128,7 @@ do_show (char *const *args) return; } if ( (GNUNET_OK != - show_signkeys (&secm[1], + show_signkeys (&secm[2], signkeys)) || (GNUNET_OK != show_denomkeys (&secm[0], @@ -3200,12 +3249,14 @@ sign_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub, * Sign @a denomkeys with offline key. * * @param secm_pub security module public key used to sign the denominations + * element 0: RSA + * element 1: CS * @param denomkeys keys to output * @param[in,out] result array where to output the signatures * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub, +sign_denomkeys (const struct TALER_SecurityModulePublicKeyP secm_pub[2], const json_t *denomkeys, json_t *result) { @@ -3300,7 +3351,7 @@ sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub, section_name, stamp_start, duration, - secm_pub, + &secm_pub[0], &secm_sig)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -3313,6 +3364,30 @@ sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub, } } break; + case TALER_DENOMINATION_CS: + { + struct TALER_CsPubHashP h_cs; + + TALER_cs_pub_hash (&denom_pub.details.cs_public_key, + &h_cs); + if (GNUNET_OK != + TALER_exchange_secmod_cs_verify (&h_cs, + section_name, + stamp_start, + duration, + &secm_pub[1], + &secm_sig)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid security module signature for denomination key %s (aborting)\n", + GNUNET_h2s (&h_denom_pub.hash)); + global_ret = EXIT_FAILURE; + test_shutdown (); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + } + break; default: global_ret = EXIT_FAILURE; test_shutdown (); @@ -3364,7 +3439,7 @@ do_sign (char *const *args) json_t *denomkeys; json_t *signkeys; struct TALER_MasterPublicKeyP mpub; - struct TALER_SecurityModulePublicKeyP secm[2]; + struct TALER_SecurityModulePublicKeyP secm[3]; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_json ("future_denoms", &denomkeys), @@ -3374,8 +3449,10 @@ do_sign (char *const *args) &mpub), GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key", &secm[0]), - GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key", + GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key", &secm[1]), + GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key", + &secm[2]), GNUNET_JSON_spec_end () }; @@ -3436,7 +3513,7 @@ do_sign (char *const *args) GNUNET_assert (NULL != signkey_sig_array); GNUNET_assert (NULL != denomkey_sig_array); if ( (GNUNET_OK != - sign_signkeys (&secm[1], + sign_signkeys (&secm[2], signkeys, signkey_sig_array)) || (GNUNET_OK != diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index 44487a3a7..e4526f1c7 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -79,6 +79,7 @@ taler_exchange_transfer_LDADD = \ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd.c taler-exchange-httpd.h \ taler-exchange-httpd_auditors.c taler-exchange-httpd_auditors.h \ + taler-exchange-httpd_csr.c taler-exchange-httpd_csr \ taler-exchange-httpd_db.c taler-exchange-httpd_db.h \ taler-exchange-httpd_deposit.c taler-exchange-httpd_deposit.h \ taler-exchange-httpd_deposits_get.c taler-exchange-httpd_deposits_get.h \ diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index ae5847d11..c357813b2 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -30,6 +30,7 @@ #include #include "taler_mhd_lib.h" #include "taler-exchange-httpd_auditors.h" +#include "taler-exchange-httpd_csr.h" #include "taler-exchange-httpd_deposit.h" #include "taler-exchange-httpd_deposits_get.h" #include "taler-exchange-httpd_extensions.h" @@ -910,6 +911,13 @@ handle_mhd_request (void *cls, .method = MHD_HTTP_METHOD_GET, .handler.get = &TEH_handler_wire }, + /* request R, used in clause schnorr withdraw and refresh */ + { + .url = "csr", + .method = MHD_HTTP_METHOD_POST, + .handler.post = &TEH_handler_csr, + .nargs = 0 + }, /* Withdrawing coins / interaction with reserves */ { .url = "reserves", diff --git a/src/exchange/taler-exchange-httpd_csr.c b/src/exchange/taler-exchange-httpd_csr.c new file mode 100644 index 000000000..0e330fe30 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_csr.c @@ -0,0 +1,178 @@ +/* + 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 +*/ +/** + * @file taler-exchange-httpd_csr.c + * @brief Handle /csr requests + * @author Lucien Heuzeveldt + * @author Gian Demarmles + */ +#include "platform.h" +#include +#include +#include "taler_json_lib.h" +#include "taler_mhd_lib.h" +#include "taler-exchange-httpd_csr.h" +#include "taler-exchange-httpd_responses.h" +#include "taler-exchange-httpd_keys.h" + + +MHD_RESULT +TEH_handler_csr (struct TEH_RequestContext *rc, + const json_t *root, + const char *const args[]) +{ + // TODO: should we have something similar to struct WithdrawContext? + // as far as I can tell this isn't necessary because we don't have + // other functions that the context should be passed to + // struct CsRContext csrc; + struct TALER_WithdrawNonce nonce; + struct TALER_DenominationHash denom_pub_hash; + struct TALER_DenominationCsPublicR r_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed ("nonce", + &nonce, + sizeof (struct TALER_WithdrawNonce)), + GNUNET_JSON_spec_fixed ("denom_pub_hash", + &denom_pub_hash, + sizeof (struct TALER_DenominationHash)), + GNUNET_JSON_spec_end () + }; + enum TALER_ErrorCode ec; + struct TEH_DenominationKey *dk; + + (void) args; + + memset (&nonce, + 0, + sizeof (nonce)); + memset (&denom_pub_hash, + 0, + sizeof (denom_pub_hash)); + memset (&r_pub, + 0, + sizeof (r_pub)); + + // parse input + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (rc->connection, + root, + spec); + GNUNET_JSON_parse_free (spec); + if (GNUNET_OK != res) + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + // check denomination referenced by denom_pub_hash + { + MHD_RESULT mret; + struct TEH_KeyStateHandle *ksh; + + ksh = TEH_keys_get_state (); + if (NULL == ksh) + { + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + NULL); + return mret; + } + dk = TEH_keys_denomination_by_hash2 (ksh, + &denom_pub_hash, + NULL, + NULL); + if (NULL == dk) + { + return TEH_RESPONSE_reply_unknown_denom_pub_hash ( + rc->connection, + &denom_pub_hash); + } + if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time)) + { + /* This denomination is past the expiration time for withdraws/refreshes*/ + return TEH_RESPONSE_reply_expired_denom_pub_hash ( + rc->connection, + &denom_pub_hash, + GNUNET_TIME_timestamp_get (), + TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, + "CSR"); + } + if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time)) + { + /* This denomination is not yet valid, no need to check + for idempotency! */ + return TEH_RESPONSE_reply_expired_denom_pub_hash ( + rc->connection, + &denom_pub_hash, + GNUNET_TIME_timestamp_get (), + TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, + "CSR"); + } + if (dk->recoup_possible) + { + /* This denomination has been revoked */ + return TEH_RESPONSE_reply_expired_denom_pub_hash ( + rc->connection, + &denom_pub_hash, + GNUNET_TIME_timestamp_get (), + TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, + "CSR"); + } + if (TALER_DENOMINATION_CS != dk->denom_pub.cipher) + { + // denomination is valid but not CS + return TEH_RESPONSE_reply_unknown_denom_pub_hash ( + rc->connection, + &denom_pub_hash); + } + } + + // derive r_pub + ec = TALER_EC_NONE; + r_pub = TEH_keys_denomination_cs_r_pub (&denom_pub_hash, + &nonce, + &ec); + if (TALER_EC_NONE != ec) + { + GNUNET_break (0); + return TALER_MHD_reply_with_ec (rc->connection, + ec, + NULL); + } + + // send response + { + MHD_RESULT ret; + + ret = TALER_MHD_REPLY_JSON_PACK ( + rc->connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_data_varsize ("r_pub_0", + &r_pub.r_pub[0], + sizeof(struct GNUNET_CRYPTO_CsRPublic)), + GNUNET_JSON_pack_data_varsize ("r_pub_1", + &r_pub.r_pub[1], + sizeof(struct GNUNET_CRYPTO_CsRPublic))); + return ret; + } +} + + +/* end of taler-exchange-httpd_csr.c */ diff --git a/src/exchange/taler-exchange-httpd_csr.h b/src/exchange/taler-exchange-httpd_csr.h new file mode 100644 index 000000000..3bd98742b --- /dev/null +++ b/src/exchange/taler-exchange-httpd_csr.h @@ -0,0 +1,43 @@ +/* + 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 +*/ +/** + * @file taler-exchange-httpd_csr.h + * @brief Handle /csr requests + * @author Lucien Heuzeveldt + * @author Gian Demarmles + */ +#ifndef TALER_EXCHANGE_HTTPD_CSR_H +#define TALER_EXCHANGE_HTTPD_CSR_H + +#include +#include "taler-exchange-httpd.h" + + +/** + * Handle a "/csr" request. Parses the "nonce" and + * the "denom_pub_hash" (identifying a denomination) used to derive the r_pub. + * + * @param rc request context + * @param root uploaded JSON data + * @param args empty array + * @return MHD result code + */ +MHD_RESULT +TEH_handler_csr (struct TEH_RequestContext *rc, + const json_t *root, + const char *const args[]); + +#endif diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index de9b81cdd..dd5928fb9 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -103,6 +103,11 @@ struct HelperDenomination */ struct TALER_RsaPubHashP h_rsa; + /** + * Hash of the CS key. + */ + struct TALER_CsPubHashP h_cs; + } h_details; /** @@ -188,7 +193,12 @@ struct HelperState /** * Handle for the denom/RSA helper. */ - struct TALER_CRYPTO_RsaDenominationHelper *dh; + struct TALER_CRYPTO_RsaDenominationHelper *rsadh; + + /** + * Handle for the denom/CS helper. + */ + struct TALER_CRYPTO_CsDenominationHelper *csdh; /** * Map from H(denom_pub) to `struct HelperDenomination` entries. @@ -200,6 +210,11 @@ struct HelperState */ struct GNUNET_CONTAINER_MultiHashMap *rsa_keys; + /** + * Map from H(cs_pub) to `struct HelperDenomination` entries. + */ + struct GNUNET_CONTAINER_MultiHashMap *cs_keys; + /** * Map from `struct TALER_ExchangePublicKey` to `struct HelperSignkey` * entries. Based on the fact that a `struct GNUNET_PeerIdentity` is also @@ -424,7 +439,12 @@ static struct GNUNET_TIME_Relative signkey_legal_duration; /** * RSA security module public key, all zero if not known. */ -static struct TALER_SecurityModulePublicKeyP denom_sm_pub; +static struct TALER_SecurityModulePublicKeyP denom_rsa_sm_pub; + +/** + * CS security module public key, all zero if not known. + */ +static struct TALER_SecurityModulePublicKeyP denom_cs_sm_pub; /** * EdDSA security module public key, all zero if not known. @@ -541,6 +561,7 @@ check_dk (void *cls, if (TALER_DENOMINATION_RSA == dk->denom_pub.cipher) GNUNET_assert (GNUNET_CRYPTO_rsa_public_key_check ( dk->denom_pub.details.rsa_public_key)); + // nothing to do for TALER_DENOMINATION_CS return GNUNET_OK; } @@ -609,19 +630,43 @@ clear_response_cache (struct TEH_KeyStateHandle *ksh) * @param sm_pub RSA security module public key to check */ static void -check_denom_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub) +check_denom_rsa_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub) { if (0 != GNUNET_memcmp (sm_pub, - &denom_sm_pub)) + &denom_rsa_sm_pub)) { - if (! GNUNET_is_zero (&denom_sm_pub)) + if (! GNUNET_is_zero (&denom_rsa_sm_pub)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Our RSA security module changed its key. This must not happen.\n"); GNUNET_assert (0); } - denom_sm_pub = *sm_pub; /* TOFU ;-) */ + denom_rsa_sm_pub = *sm_pub; /* TOFU ;-) */ + } +} + + +/** + * Check that the given CS security module's public key is the one + * we have pinned. If it does not match, we die hard. + * + * @param sm_pub RSA security module public key to check + */ +static void +check_denom_cs_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub) +{ + if (0 != + GNUNET_memcmp (sm_pub, + &denom_cs_sm_pub)) + { + if (! GNUNET_is_zero (&denom_cs_sm_pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Our CS security module changed its key. This must not happen.\n"); + GNUNET_assert (0); + } + denom_cs_sm_pub = *sm_pub; /* TOFU ;-) */ } } @@ -712,6 +757,8 @@ destroy_key_helpers (struct HelperState *hs) hs); GNUNET_CONTAINER_multihashmap_destroy (hs->rsa_keys); hs->rsa_keys = NULL; + GNUNET_CONTAINER_multihashmap_destroy (hs->cs_keys); + hs->cs_keys = NULL; GNUNET_CONTAINER_multihashmap_destroy (hs->denom_keys); hs->denom_keys = NULL; GNUNET_CONTAINER_multipeermap_iterate (hs->esign_keys, @@ -719,10 +766,15 @@ destroy_key_helpers (struct HelperState *hs) hs); GNUNET_CONTAINER_multipeermap_destroy (hs->esign_keys); hs->esign_keys = NULL; - if (NULL != hs->dh) + if (NULL != hs->rsadh) + { + TALER_CRYPTO_helper_rsa_disconnect (hs->rsadh); + hs->rsadh = NULL; + } + if (NULL != hs->csdh) { - TALER_CRYPTO_helper_rsa_disconnect (hs->dh); - hs->dh = NULL; + TALER_CRYPTO_helper_cs_disconnect (hs->csdh); + hs->csdh = NULL; } if (NULL != hs->esh) { @@ -795,7 +847,7 @@ load_age_mask (const char*section_name) * zero if the key has been revoked or purged * @param validity_duration how long does the key remain available for signing; * zero if the key has been revoked or purged - * @param h_denom_pub hash of the @a denom_pub that is available (or was purged) + * @param h_rsa hash of the @a denom_pub that is available (or was purged) * @param denom_pub the public key itself, NULL if the key was revoked or purged * @param sm_pub public key of the security module, NULL if the key was revoked or purged * @param sm_sig signature from the security module, NULL if the key was revoked or purged @@ -832,7 +884,7 @@ helper_rsa_cb ( return; } GNUNET_assert (NULL != sm_pub); - check_denom_sm_pub (sm_pub); + check_denom_rsa_sm_pub (sm_pub); hd = GNUNET_new (struct HelperDenomination); hd->start_time = start_time; hd->validity_duration = validity_duration; @@ -864,6 +916,87 @@ helper_rsa_cb ( } +/** + * Function called with information about available CS keys for signing. Usually + * only called once per key upon connect. Also called again in case a key is + * being revoked, in that case with an @a end_time of zero. + * + * @param cls closure with the `struct HelperState *` + * @param section_name name of the denomination type in the configuration; + * NULL if the key has been revoked or purged + * @param start_time when does the key become available for signing; + * zero if the key has been revoked or purged + * @param validity_duration how long does the key remain available for signing; + * zero if the key has been revoked or purged + * @param h_cs hash of the @a denom_pub that is available (or was purged) + * @param denom_pub the public key itself, NULL if the key was revoked or purged + * @param sm_pub public key of the security module, NULL if the key was revoked or purged + * @param sm_sig signature from the security module, NULL if the key was revoked or purged + * The signature was already verified against @a sm_pub. + */ +static void +helper_cs_cb ( + void *cls, + const char *section_name, + struct GNUNET_TIME_Timestamp start_time, + struct GNUNET_TIME_Relative validity_duration, + const struct TALER_CsPubHashP *h_cs, + const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_SecurityModulePublicKeyP *sm_pub, + const struct TALER_SecurityModuleSignatureP *sm_sig) +{ + struct HelperState *hs = cls; + struct HelperDenomination *hd; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "CS helper announces key %s for denomination type %s with validity %s\n", + GNUNET_h2s (&h_cs->hash), + section_name, + GNUNET_STRINGS_relative_time_to_string (validity_duration, + GNUNET_NO)); + key_generation++; + TEH_resume_keys_requests (false); + hd = GNUNET_CONTAINER_multihashmap_get (hs->cs_keys, + &h_cs->hash); + if (NULL != hd) + { + /* should be just an update (revocation!), so update existing entry */ + hd->validity_duration = validity_duration; + return; + } + GNUNET_assert (NULL != sm_pub); + check_denom_cs_sm_pub (sm_pub); + hd = GNUNET_new (struct HelperDenomination); + hd->start_time = start_time; + hd->validity_duration = validity_duration; + hd->h_details.h_cs = *h_cs; + hd->sm_sig = *sm_sig; + GNUNET_assert (TALER_DENOMINATION_CS == denom_pub->cipher); + TALER_denom_pub_deep_copy (&hd->denom_pub, + denom_pub); + GNUNET_assert (TALER_DENOMINATION_CS == hd->denom_pub.cipher); + /* load the age mask for the denomination, if applicable */ + hd->denom_pub.age_mask = load_age_mask (section_name); + TALER_denom_pub_hash (&hd->denom_pub, + &hd->h_denom_pub); + hd->section_name = GNUNET_strdup (section_name); + GNUNET_assert ( + GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put ( + hs->denom_keys, + &hd->h_denom_pub.hash, + hd, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + GNUNET_assert ( + GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put ( + hs->cs_keys, + &hd->h_details.h_cs.hash, + hd, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); +} + + /** * Function called with information about available keys for signing. Usually * only called once per key upon connect. Also called again in case a key is @@ -940,13 +1073,24 @@ setup_key_helpers (struct HelperState *hs) hs->rsa_keys = GNUNET_CONTAINER_multihashmap_create (1024, GNUNET_YES); + hs->cs_keys + = GNUNET_CONTAINER_multihashmap_create (1024, + GNUNET_YES); hs->esign_keys = GNUNET_CONTAINER_multipeermap_create (32, GNUNET_NO /* MUST BE NO! */); - hs->dh = TALER_CRYPTO_helper_rsa_connect (TEH_cfg, - &helper_rsa_cb, - hs); - if (NULL == hs->dh) + hs->rsadh = TALER_CRYPTO_helper_rsa_connect (TEH_cfg, + &helper_rsa_cb, + hs); + if (NULL == hs->rsadh) + { + destroy_key_helpers (hs); + return GNUNET_SYSERR; + } + hs->csdh = TALER_CRYPTO_helper_cs_connect (TEH_cfg, + &helper_cs_cb, + hs); + if (NULL == hs->csdh) { destroy_key_helpers (hs); return GNUNET_SYSERR; @@ -971,7 +1115,8 @@ setup_key_helpers (struct HelperState *hs) static void sync_key_helpers (struct HelperState *hs) { - TALER_CRYPTO_helper_rsa_poll (hs->dh); + TALER_CRYPTO_helper_rsa_poll (hs->rsadh); + TALER_CRYPTO_helper_cs_poll (hs->csdh); TALER_CRYPTO_helper_esign_poll (hs->esh); } @@ -2292,11 +2437,12 @@ TEH_keys_denomination_sign (const struct TALER_DenominationHash *h_denom_pub, switch (hd->denom_pub.cipher) { case TALER_DENOMINATION_RSA: - return TALER_CRYPTO_helper_rsa_sign (ksh->helpers->dh, + return TALER_CRYPTO_helper_rsa_sign (ksh->helpers->rsadh, &hd->h_details.h_rsa, msg, msg_size, ec); + // TODO: case TALER_DENOMINATION_CS: default: *ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; return none; @@ -2304,6 +2450,45 @@ TEH_keys_denomination_sign (const struct TALER_DenominationHash *h_denom_pub, } +struct TALER_DenominationCsPublicR +TEH_keys_denomination_cs_r_pub (const struct + TALER_DenominationHash *h_denom_pub, + const struct TALER_WithdrawNonce *nonce, + enum TALER_ErrorCode *ec) +{ + struct TEH_KeyStateHandle *ksh; + struct TALER_DenominationCsPublicR none; + struct HelperDenomination *hd; + + memset (&none, + 0, + sizeof (none)); + ksh = TEH_keys_get_state (); + if (NULL == ksh) + { + *ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING; + return none; + } + hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys, + &h_denom_pub->hash); + if (NULL == hd) + { + *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; + return none; + } + if (TALER_DENOMINATION_CS != hd->denom_pub.cipher) + { + *ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + return none; + } + + return TALER_CRYPTO_helper_cs_r_derive (ksh->helpers->csdh, + &hd->h_details.h_cs, + nonce, + ec); +} + + void TEH_keys_denomination_revoke (const struct TALER_DenominationHash *h_denom_pub) { @@ -2326,10 +2511,15 @@ TEH_keys_denomination_revoke (const struct TALER_DenominationHash *h_denom_pub) switch (hd->denom_pub.cipher) { case TALER_DENOMINATION_RSA: - TALER_CRYPTO_helper_rsa_revoke (ksh->helpers->dh, + TALER_CRYPTO_helper_rsa_revoke (ksh->helpers->rsadh, &hd->h_details.h_rsa); TEH_keys_update_states (); return; + case TALER_DENOMINATION_CS: + TALER_CRYPTO_helper_cs_revoke (ksh->helpers->csdh, + &hd->h_details.h_cs); + TEH_keys_update_states (); + return; default: GNUNET_break (0); return; @@ -2923,7 +3113,14 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh, .signkeys = json_array () }; - if (GNUNET_is_zero (&denom_sm_pub)) + if (GNUNET_is_zero (&denom_rsa_sm_pub)) + { + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_GATEWAY, + TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE, + NULL); + } + if (GNUNET_is_zero (&denom_cs_sm_pub)) { return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_GATEWAY, @@ -2954,7 +3151,9 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh, GNUNET_JSON_pack_data_auto ("master_pub", &TEH_master_public_key), GNUNET_JSON_pack_data_auto ("denom_secmod_public_key", - &denom_sm_pub), + &denom_rsa_sm_pub), + GNUNET_JSON_pack_data_auto ("denom_secmod_cs_public_key", + &denom_cs_sm_pub), GNUNET_JSON_pack_data_auto ("signkey_secmod_public_key", &esign_sm_pub)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, diff --git a/src/exchange/taler-exchange-httpd_keys.h b/src/exchange/taler-exchange-httpd_keys.h index ce9068ec2..0134a28d0 100644 --- a/src/exchange/taler-exchange-httpd_keys.h +++ b/src/exchange/taler-exchange-httpd_keys.h @@ -184,6 +184,23 @@ TEH_keys_denomination_sign (const struct TALER_DenominationHash *h_denom_pub, enum TALER_ErrorCode *ec); +/** + * Request to derive CS r_pub using the denomination corresponding to @a h_denom_pub + * and @a nonce. + * + * @param h_denom_pub hash of the public key to use to derive r_pub + * @param nonce withdraw/refresh nonce + * @param[out] ec set to the error code (or #TALER_EC_NONE on success) + * @return r_pub, the value inside the structure will be NULL on failure, + * see @a ec for details about the failure + */ +struct TALER_DenominationCsPublicR +TEH_keys_denomination_cs_r_pub (const struct + TALER_DenominationHash *h_denom_pub, + const struct TALER_WithdrawNonce *nonce, + enum TALER_ErrorCode *ec); + + /** * Revoke the public key associated with @param h_denom_pub . * This function should be called AFTER the database was diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index e1f632a6f..b4142d6f9 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -1112,6 +1112,17 @@ TALER_EXCHANGE_csr (struct TALER_EXCHANGE_Handle *exchange, void *res_cb_cls); +/** + * + * Cancel a CS R request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param csrh the withdraw handle + */ +void +TALER_EXCHANGE_csr_cancel (struct TALER_EXCHANGE_CsRHandle *csrh); + + /* ********************* GET /reserves/$RESERVE_PUB *********************** */ @@ -2576,10 +2587,15 @@ struct TALER_EXCHANGE_FutureKeys struct TALER_SecurityModulePublicKeyP signkey_secmod_public_key; /** - * Public key of the denomination security module. + * Public key of the RSA denomination security module. */ struct TALER_SecurityModulePublicKeyP denom_secmod_public_key; + /** + * Public key of the CS denomination security module. + */ + struct TALER_SecurityModulePublicKeyP denom_secmod_cs_public_key; + /** * Offline master public key used by this exchange. */ diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h index d243dd723..51ebe6d90 100644 --- a/src/include/taler_json_lib.h +++ b/src/include/taler_json_lib.h @@ -299,18 +299,6 @@ TALER_JSON_spec_i18n_str (const char *name, const char **strptr); -/** - * Generate line in parser specification for a CS R. - * - * @param field name of the field - * @param r_pub where the r_pub has to be written - * @return corresponding field spec - */ -struct GNUNET_JSON_Specification -TALER_JSON_spec_csr (const char *field, - struct GNUNET_CRYPTO_CsRPublic *r_pub); - - /** * Hash a JSON for binary signing. * diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index 20e3145f0..d5746c5c8 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -66,11 +66,13 @@ TALER_TESTING_make_wire_details (const char *payto); * * @param keys array of keys to search * @param amount coin value to look for + * @param cipher denomination cipher * @return NULL if no matching key was found */ const struct TALER_EXCHANGE_DenomPublicKey * TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, - const struct TALER_Amount *amount); + const struct TALER_Amount *amount, + const enum TALER_DenominationCipher cipher); /** @@ -1288,6 +1290,24 @@ TALER_TESTING_cmd_withdraw_amount (const char *label, unsigned int expected_response_code); +/** + * Create a withdraw command using a CS denomination, letting the caller specify + * the desired amount as string. + * + * @param label command label. + * @param reserve_reference command providing us with a reserve to withdraw from + * @param amount how much we withdraw. + * @param expected_response_code which HTTP response code + * we expect from the exchange. + * @return the withdraw command to be executed by the interpreter. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_withdraw_cs_amount (const char *label, + const char *reserve_reference, + const char *amount, + unsigned int expected_response_code); + + /** * Create a withdraw command, letting the caller specify * the desired amount as string and also re-using an existing diff --git a/src/json/json_helper.c b/src/json/json_helper.c index ef1617ef3..c07129d1a 100644 --- a/src/json/json_helper.c +++ b/src/json/json_helper.c @@ -262,6 +262,26 @@ parse_denom_pub (void *cls, GNUNET_JSON_spec_end () }; + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &emsg, + &eline)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; + } + case TALER_DENOMINATION_CS: + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed ("cs_public_key", + &denom_pub->details.cs_public_key, + sizeof (denom_pub->details.cs_public_key)), + GNUNET_JSON_spec_end () + }; + if (GNUNET_OK != GNUNET_JSON_parse (root, ispec, @@ -686,7 +706,7 @@ TALER_JSON_parse_agemask (const json_t *root, { return GNUNET_SYSERR; } - +//FIXME: return GNUNET_OK; /** * Parse given JSON object to CS R. diff --git a/src/json/json_pack.c b/src/json/json_pack.c index 6fea72a39..869867189 100644 --- a/src/json/json_pack.c +++ b/src/json/json_pack.c @@ -68,6 +68,17 @@ TALER_JSON_pack_denom_pub ( GNUNET_JSON_pack_rsa_public_key ("rsa_public_key", pk->details.rsa_public_key)); break; + case TALER_DENOMINATION_CS: + ps.object + = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("cipher", + TALER_DENOMINATION_CS), + GNUNET_JSON_pack_uint64 ("age_mask", + pk->age_mask.mask), + GNUNET_JSON_pack_data_varsize ("cs_public_key", + &pk->details.cs_public_key, + sizeof (pk->details.cs_public_key))); + break; default: GNUNET_assert (0); } @@ -94,6 +105,7 @@ TALER_JSON_pack_denom_sig ( GNUNET_JSON_pack_rsa_signature ("rsa_signature", sig->details.rsa_signature)); break; + // TODO: case TALER_DENOMINATION_CS: default: GNUNET_assert (0); } @@ -120,6 +132,7 @@ TALER_JSON_pack_blinded_denom_sig ( GNUNET_JSON_pack_rsa_signature ("blinded_rsa_signature", sig->details.blinded_rsa_signature)); break; + // TODO: case TALER_DENOMINATION_CS: default: GNUNET_assert (0); } diff --git a/src/lib/exchange_api_csr.c b/src/lib/exchange_api_csr.c index fa7010f2c..a3f631181 100644 --- a/src/lib/exchange_api_csr.c +++ b/src/lib/exchange_api_csr.c @@ -90,13 +90,16 @@ struct TALER_EXCHANGE_CsRHandle * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors */ static enum GNUNET_GenericReturnValue -csr_ok (struct TALER_EXCHANGE_CsRHandle *csrh, - const json_t *json, +csr_ok (const json_t *json, struct TALER_EXCHANGE_CsRResponse *csrr) { struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_csr ("r_pub_0", &csrr->details.success.r_pubs.r_pub[0]), - TALER_JSON_spec_csr ("r_pub_1", &csrr->details.success.r_pubs.r_pub[1]), + GNUNET_JSON_spec_fixed ("r_pub_0", + &csrr->details.success.r_pubs.r_pub[0], + sizeof (struct GNUNET_CRYPTO_CsRPublic)), + GNUNET_JSON_spec_fixed ("r_pub_1", + &csrr->details.success.r_pubs.r_pub[1], + sizeof (struct GNUNET_CRYPTO_CsRPublic)), GNUNET_JSON_spec_end () }; @@ -109,37 +112,11 @@ csr_ok (struct TALER_EXCHANGE_CsRHandle *csrh, return GNUNET_SYSERR; } - /* r_pubs are valid, return it to the application */ - csrh->cb (csrh->cb_cls, - csrr); - /* make sure callback isn't called again after return */ - csrh->cb = NULL; GNUNET_JSON_parse_free (spec); return GNUNET_OK; } -/** - * - * Cancel a CS R request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param csrh the withdraw handle - */ -void -TALER_EXCHANGE_csr_cancel (struct TALER_EXCHANGE_CsRHandle *csrh) -{ - if (NULL != csrh->job) - { - GNUNET_CURL_job_cancel (csrh->job); - csrh->job = NULL; - } - GNUNET_free (csrh->url); - TALER_curl_easy_post_finished (&csrh->post_ctx); - GNUNET_free (csrh); -} - - /** * Function called when we're done processing the HTTP /csr request. * @@ -170,8 +147,7 @@ handle_csr_finished (void *cls, break; case MHD_HTTP_OK: if (GNUNET_OK != - csr_ok (csrh, - j, + csr_ok (j, &csrr)) { GNUNET_break_op (0); @@ -179,9 +155,7 @@ handle_csr_finished (void *cls, csrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - GNUNET_assert (NULL == csrh->cb); - TALER_EXCHANGE_csr_cancel (csrh); - return; + break; case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the exchange is buggy (or API version conflict); just pass JSON reply to the application */ @@ -190,8 +164,8 @@ handle_csr_finished (void *cls, break; case MHD_HTTP_NOT_FOUND: /* Nothing really to verify, the exchange basically just says - that it doesn't know the /csr. Can happen if the exchange - doesn't support Clause Schnorr. + that it doesn't know the /csr endpoint or denomination. + Can happen if the exchange doesn't support Clause Schnorr. We should simply pass the JSON reply to the application. */ csrr.hr.ec = TALER_JSON_get_error_code (j); csrr.hr.hint = TALER_JSON_get_error_hint (j); @@ -221,12 +195,9 @@ handle_csr_finished (void *cls, (int) hr.ec); break; } - if (NULL != csrh->cb) - { - csrh->cb (csrh->cb_cls, - &csrr); - csrh->cb = NULL; - } + csrh->cb (csrh->cb_cls, + &csrr); + csrh->cb = NULL; TALER_EXCHANGE_csr_cancel (csrh); } @@ -305,3 +276,17 @@ TALER_EXCHANGE_csr (struct TALER_EXCHANGE_Handle *exchange, return csrh; } + + +void +TALER_EXCHANGE_csr_cancel (struct TALER_EXCHANGE_CsRHandle *csrh) +{ + if (NULL != csrh->job) + { + GNUNET_CURL_job_cancel (csrh->job); + csrh->job = NULL; + } + GNUNET_free (csrh->url); + TALER_curl_easy_post_finished (&csrh->post_ctx); + GNUNET_free (csrh); +} diff --git a/src/lib/exchange_api_management_get_keys.c b/src/lib/exchange_api_management_get_keys.c index e776082d3..4d6866338 100644 --- a/src/lib/exchange_api_management_get_keys.c +++ b/src/lib/exchange_api_management_get_keys.c @@ -92,6 +92,8 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh, &fk.master_pub), GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key", &fk.denom_secmod_public_key), + GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key", + &fk.denom_secmod_cs_public_key), GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key", &fk.signkey_secmod_public_key), GNUNET_JSON_spec_end () @@ -243,6 +245,26 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh, } } break; + case TALER_DENOMINATION_CS: + { + struct TALER_CsPubHashP h_cs; + + TALER_cs_pub_hash (&denom_key->key.details.cs_public_key, + &h_cs); + if (GNUNET_OK != + TALER_exchange_secmod_cs_verify (&h_cs, + section_name, + denom_key->valid_from, + duration, + &fk.denom_secmod_cs_public_key, + &denom_key->denom_secmod_sig)) + { + GNUNET_break_op (0); + ok = false; + break; + } + } + break; default: GNUNET_break_op (0); ok = false; diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c index a8bce5e71..91838d4ce 100644 --- a/src/lib/exchange_api_withdraw.c +++ b/src/lib/exchange_api_withdraw.c @@ -73,6 +73,11 @@ struct TALER_EXCHANGE_WithdrawHandle */ struct TALER_CoinPubHash c_hash; + /** + * Handler for the CS R request (only used for TALER_DENOMINATION_CS denominations) + */ + struct TALER_EXCHANGE_CsRHandle *csrh; + }; @@ -147,6 +152,37 @@ handle_reserve_withdraw_finished ( } +/** + * Function called when stage 1 of CS withdraw is finished (request r_pub's) + * + * @param cls + */ +static void +withdraw_cs_stage_two_callback (void *cls, + const struct TALER_EXCHANGE_CsRResponse *csrr) +{ + struct TALER_EXCHANGE_WithdrawHandle *wh = cls; + // TODO: this should only be set for non-OK cases + struct TALER_EXCHANGE_WithdrawResponse wr = { + .hr = csrr->hr + }; + + // switch (csrr->hr.http_status) + // { + // case MHD_HTTP_OK: + // // TODO: implement rest of withdraw + // break; + // default: + // break; + // } + + // TODO: this should only be called for non-OK cases + wh->cb (wh->cb_cls, + &wr); + TALER_EXCHANGE_withdraw_cancel (wh); +} + + /** * Withdraw a coin from the exchange using a /reserve/withdraw request. Note * that to ensure that no money is lost in case of hardware failures, @@ -183,31 +219,54 @@ TALER_EXCHANGE_withdraw ( wh->cb_cls = res_cb_cls; wh->pk = *pk; wh->ps = *ps; - if (GNUNET_OK != - TALER_planchet_prepare (&pk->key, - ps, - &wh->c_hash, - &pd)) + wh->csrh = NULL; + switch (pk->key.cipher) { + case TALER_DENOMINATION_RSA: + if (GNUNET_OK != + TALER_planchet_prepare (&pk->key, + ps, + &wh->c_hash, + &pd)) + { + GNUNET_break (0); + GNUNET_free (wh); + return NULL; + } + TALER_denom_pub_deep_copy (&wh->pk.key, + &pk->key); + wh->wh2 = TALER_EXCHANGE_withdraw2 (exchange, + &pd, + reserve_priv, + &handle_reserve_withdraw_finished, + wh); + GNUNET_free (pd.blinded_planchet.details.rsa_blinded_planchet.blinded_msg); + return wh; + case TALER_DENOMINATION_CS: + struct TALER_WithdrawNonce nonce; + TALER_cs_withdraw_nonce_derive (&ps->coin_priv, &nonce); + wh->csrh = TALER_EXCHANGE_csr (exchange, + pk, + &nonce, + &withdraw_cs_stage_two_callback, + wh); + return wh; + default: GNUNET_break (0); GNUNET_free (wh); return NULL; } - TALER_denom_pub_deep_copy (&wh->pk.key, - &pk->key); - wh->wh2 = TALER_EXCHANGE_withdraw2 (exchange, - &pd, - reserve_priv, - &handle_reserve_withdraw_finished, - wh); - GNUNET_free (pd.blinded_planchet.details.rsa_blinded_planchet.blinded_msg); - return wh; } void TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh) { + if (NULL != wh->csrh) + { + TALER_EXCHANGE_csr_cancel (wh->csrh); + wh->csrh = NULL; + } if (NULL != wh->wh2) { TALER_EXCHANGE_withdraw2_cancel (wh->wh2); diff --git a/src/pq/pq_query_helper.c b/src/pq/pq_query_helper.c index 37d7bf5be..c04161d0e 100644 --- a/src/pq/pq_query_helper.c +++ b/src/pq/pq_query_helper.c @@ -194,7 +194,9 @@ qconv_denom_pub (void *cls, denom_pub->details.rsa_public_key, &tbuf); break; - // TODO: add case for Clause-Schnorr + case TALER_DENOMINATION_CS: + tlen = sizeof (denom_pub->details.cs_public_key); + break; default: GNUNET_assert (0); } @@ -211,7 +213,11 @@ qconv_denom_pub (void *cls, tlen); GNUNET_free (tbuf); break; - // TODO: add case for Clause-Schnorr + case TALER_DENOMINATION_CS: + memcpy (&buf[sizeof (be)], + &denom_pub->details.cs_public_key, + tlen); + break; default: GNUNET_assert (0); } diff --git a/src/pq/pq_result_helper.c b/src/pq/pq_result_helper.c index a6bc9409a..d6b0eb7bb 100644 --- a/src/pq/pq_result_helper.c +++ b/src/pq/pq_result_helper.c @@ -425,7 +425,16 @@ extract_denom_pub (void *cls, return GNUNET_SYSERR; } return GNUNET_OK; - // FIXME: add CS case! + case TALER_DENOMINATION_CS: + if (sizeof (pk->details.cs_public_key) != len) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + memcpy (&pk->details.cs_public_key, + res, + len); + return GNUNET_OK; default: GNUNET_break (0); } diff --git a/src/testing/.gitignore b/src/testing/.gitignore index f721009e6..61e3a4c06 100644 --- a/src/testing/.gitignore +++ b/src/testing/.gitignore @@ -24,12 +24,15 @@ test_taler_exchange_httpd_home/.local/share/taler/taler-exchange-secmod-eddsa/ test_taler_exchange_httpd_home/.local/share/taler/taler-exchange-secmod-rsa/ test_exchange_api_keys_cherry_picking_home/.local/share/taler/crypto-rsa/ test_exchange_api_home/.local/share/taler/exchange-offline/secm_tofus.pub +test_exchange_api_home/.local/share/taler/exchange-secmod-cs/ test_exchange_api_home/.local/share/taler/exchange-secmod-eddsa/ test_exchange_api_home/.local/share/taler/exchange-secmod-rsa/ test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-offline/secm_tofus.pub +test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-secmod-cs/ test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-secmod-eddsa/ test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-secmod-rsa/ test_taler_exchange_httpd_home/.local/share/taler/exchange-offline/secm_tofus.pub +test_taler_exchange_httpd_home/.local/share/taler/exchange-secmod-cs/ test_taler_exchange_httpd_home/.local/share/taler/exchange-secmod-eddsa/ test_taler_exchange_httpd_home/.local/share/taler/exchange-secmod-rsa/ test_kyc_api diff --git a/src/testing/test_auditor_api.conf b/src/testing/test_auditor_api.conf index 03a5e2453..0b08d27ef 100644 --- a/src/testing/test_auditor_api.conf +++ b/src/testing/test_auditor_api.conf @@ -10,6 +10,10 @@ TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/ # Reduce from 1 year to speed up test LOOKAHEAD_SIGN = 24 days +[taler-exchange-secmod-cs] +# Reduce from 1 year to speed up test +LOOKAHEAD_SIGN = 24 days + [taler-exchange-secmod-eddsa] # Reduce from 1 year to speed up test LOOKAHEAD_SIGN = 24 days diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c index 59c2cb06d..29a3e5c6f 100644 --- a/src/testing/test_exchange_api.c +++ b/src/testing/test_exchange_api.c @@ -406,6 +406,60 @@ run (void *cls, TALER_TESTING_cmd_end () }; + /** + * Test CS withdrawal plus spending. + */ + struct TALER_TESTING_Command withdraw_cs[] = { + /** + * Move money to the exchange's bank account. + */ + CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1", + "EUR:6.02"), + TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-1", + "EUR:6.02", + bc.user42_payto, + bc.exchange_payto, + "create-reserve-1"), + /** + * Make a reserve exist, according to the previous + * transfer. + */ + CMD_EXEC_WIREWATCH ("wirewatch-1"), + /** + * Withdraw EUR:5. + */ + TALER_TESTING_cmd_withdraw_cs_amount ("withdraw-cs-coin-1", + "create-reserve-1", + "EUR:5", + MHD_HTTP_OK), + // TODO: rest of the tests + // /** + // * Withdraw EUR:1 using the SAME private coin key as for the previous coin + // * (in violation of the specification, to be detected on spending!). + // */ + // TALER_TESTING_cmd_withdraw_amount_reuse_key ("withdraw-coin-1x", + // "create-reserve-1", + // "EUR:1", + // "withdraw-coin-1", + // MHD_HTTP_OK), + // /** + // * Check the reserve is depleted. + // */ + // TALER_TESTING_cmd_status ("status-1", + // "create-reserve-1", + // "EUR:0", + // MHD_HTTP_OK), + // /* + // * Try to overdraw. + // */ + // TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2", + // "create-reserve-1", + // "EUR:5", + // MHD_HTTP_CONFLICT), + TALER_TESTING_cmd_end () + }; + + // TODO: CS related tests /** * This block checks whether a wire deadline @@ -953,6 +1007,9 @@ run (void *cls, refresh), TALER_TESTING_cmd_batch ("track", track), + TALER_TESTING_cmd_batch ("withdraw-cs", + withdraw_cs), + // TODO: Clause Schnorr related tests TALER_TESTING_cmd_batch ("unaggregation", unaggregation), TALER_TESTING_cmd_batch ("aggregation", diff --git a/src/testing/test_exchange_api_keys_cherry_picking.conf b/src/testing/test_exchange_api_keys_cherry_picking.conf index d7dd95352..14f897c5d 100644 --- a/src/testing/test_exchange_api_keys_cherry_picking.conf +++ b/src/testing/test_exchange_api_keys_cherry_picking.conf @@ -22,6 +22,10 @@ CURRENCY = EUR # Reduce from 1 year to speed up test LOOKAHEAD_SIGN = 24 days +[taler-exchange-secmod-cs] +# Reduce from 1 year to speed up test +LOOKAHEAD_SIGN = 24 days + [taler-exchange-secmod-eddsa] # Reduce from 1 year to speed up test LOOKAHEAD_SIGN = 24 days @@ -81,6 +85,10 @@ HTTP_PORT=8082 OVERLAP_DURATION = 1 s LOOKAHEAD_SIGN = 20 s +[taler-exchange-secmod-cs] +OVERLAP_DURATION = 1 s +LOOKAHEAD_SIGN = 20 s + [taler-exchange-secmod-eddsa] OVERLAP_DURATION = 1 s DURATION = 30 s diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c index d2c2c714c..0b47f5080 100644 --- a/src/testing/testing_api_cmd_refresh.c +++ b/src/testing/testing_api_cmd_refresh.c @@ -1048,8 +1048,10 @@ melt_run (void *cls, TALER_TESTING_interpreter_fail (rms->is); return; } - fresh_pk = TALER_TESTING_find_pk - (TALER_EXCHANGE_get_keys (is->exchange), &fresh_amount); + fresh_pk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange), + &fresh_amount, + // FIXME: replace hardcoded value + TALER_DENOMINATION_RSA); if (NULL == fresh_pk) { GNUNET_break (0); diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c index e87f42c34..e07eac341 100644 --- a/src/testing/testing_api_cmd_withdraw.c +++ b/src/testing/testing_api_cmd_withdraw.c @@ -72,6 +72,11 @@ struct WithdrawState */ struct TALER_Amount amount; + /** + * Type of denomination that we should withdraw + */ + enum TALER_DenominationCipher cipher; + /** * If @e amount is NULL, this specifies the denomination key to * use. Otherwise, this will be set (by the interpreter) to the @@ -261,6 +266,13 @@ reserve_withdraw_cb (void *cls, switch (wr->hr.http_status) { case MHD_HTTP_OK: + // TODO: remove + // temporary make test successful when CS + if (TALER_DENOMINATION_CS == ws->cipher) + { + break; + } + TALER_denom_sig_deep_copy (&ws->sig, &wr->details.success.sig); if (0 != ws->total_backoff.rel_value_us) @@ -388,7 +400,7 @@ withdraw_run (void *cls, &ws->reserve_pub); if (NULL == ws->reuse_coin_key_ref) { - TALER_planchet_setup_random (&ws->ps, TALER_DENOMINATION_RSA); + TALER_planchet_setup_random (&ws->ps, ws->cipher); } else { @@ -409,13 +421,14 @@ withdraw_run (void *cls, TALER_TESTING_get_trait_coin_priv (cref, index, &coin_priv)); - TALER_planchet_setup_random (&ws->ps, TALER_DENOMINATION_RSA); + TALER_planchet_setup_random (&ws->ps, ws->cipher); ws->ps.coin_priv = *coin_priv; } if (NULL == ws->pk) { dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange), - &ws->amount); + &ws->amount, + ws->cipher); if (NULL == dpk) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -556,6 +569,44 @@ TALER_TESTING_cmd_withdraw_amount (const char *label, const char *reserve_reference, const char *amount, unsigned int expected_response_code) +{ + // TODO: ATM this is hardcoded to RSA denominations + // (use TALER_TESTING_cmd_withdraw_cs_amount for Clause Schnorr) + struct WithdrawState *ws; + + ws = GNUNET_new (struct WithdrawState); + ws->reserve_reference = reserve_reference; + if (GNUNET_OK != + TALER_string_to_amount (amount, + &ws->amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %s\n", + amount, + label); + GNUNET_assert (0); + } + ws->expected_response_code = expected_response_code; + ws->cipher = TALER_DENOMINATION_RSA; + { + struct TALER_TESTING_Command cmd = { + .cls = ws, + .label = label, + .run = &withdraw_run, + .cleanup = &withdraw_cleanup, + .traits = &withdraw_traits + }; + + return cmd; + } +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_withdraw_cs_amount (const char *label, + const char *reserve_reference, + const char *amount, + unsigned int expected_response_code) { struct WithdrawState *ws; @@ -572,6 +623,7 @@ TALER_TESTING_cmd_withdraw_amount (const char *label, GNUNET_assert (0); } ws->expected_response_code = expected_response_code; + ws->cipher = TALER_DENOMINATION_CS; { struct TALER_TESTING_Command cmd = { .cls = ws, @@ -656,6 +708,7 @@ TALER_TESTING_cmd_withdraw_denomination ( ws->reserve_reference = reserve_reference; ws->pk = TALER_EXCHANGE_copy_denomination_key (dk); ws->expected_response_code = expected_response_code; + ws->cipher = dk->key.cipher; { struct TALER_TESTING_Command cmd = { .cls = ws, diff --git a/src/testing/testing_api_helpers_exchange.c b/src/testing/testing_api_helpers_exchange.c index fe7588107..a30db0336 100644 --- a/src/testing/testing_api_helpers_exchange.c +++ b/src/testing/testing_api_helpers_exchange.c @@ -416,11 +416,13 @@ TALER_TESTING_prepare_exchange (const char *config_filename, * * @param keys array of keys to search * @param amount coin value to look for + * @param cipher denomination cipher * @return NULL if no matching key was found */ const struct TALER_EXCHANGE_DenomPublicKey * TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, - const struct TALER_Amount *amount) + const struct TALER_Amount *amount, + const enum TALER_DenominationCipher cipher) { struct GNUNET_TIME_Timestamp now; struct TALER_EXCHANGE_DenomPublicKey *pk; @@ -430,6 +432,8 @@ TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, for (unsigned int i = 0; inum_denom_keys; i++) { pk = &keys->denom_keys[i]; + if (cipher != pk->key.cipher) + continue; if ( (0 == TALER_amount_cmp (amount, &pk->value)) && (GNUNET_TIME_timestamp_cmp (now, @@ -446,6 +450,8 @@ TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, for (unsigned int i = 0; inum_denom_keys; i++) { pk = &keys->denom_keys[i]; + if (cipher != pk->key.cipher) + continue; if ( (0 == TALER_amount_cmp (amount, &pk->value)) && (GNUNET_TIME_timestamp_cmp (now, @@ -467,6 +473,25 @@ TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, return NULL; } } + // do 3rd pass to check if cipher type is to blame for failure + for (unsigned int i = 0; inum_denom_keys; i++) + { + pk = &keys->denom_keys[i]; + if ( (0 == TALER_amount_cmp (amount, + &pk->value)) && + (cipher != pk->key.cipher) ) + { + GNUNET_log + (GNUNET_ERROR_TYPE_WARNING, + "Have denomination key for `%s', but with wrong" + " cipher type %d vs %d\n", + str, + cipher, + pk->key.cipher); + GNUNET_free (str); + return NULL; + } + } GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "No denomination key for amount %s found\n", str); @@ -608,9 +633,9 @@ TALER_TESTING_setup_with_exchange (TALER_TESTING_Main main_cb, * @param[in] helpers the process handles. */ static void -stop_helpers (struct GNUNET_OS_Process *helpers[2]) +stop_helpers (struct GNUNET_OS_Process *helpers[3]) { - for (unsigned int i = 0; i<2; i++) + for (unsigned int i = 0; i<3; i++) { if (NULL == helpers[i]) continue; @@ -632,7 +657,7 @@ stop_helpers (struct GNUNET_OS_Process *helpers[2]) */ static enum GNUNET_GenericReturnValue start_helpers (const char *config_filename, - struct GNUNET_OS_Process *helpers[2]) + struct GNUNET_OS_Process *helpers[3]) { char *dir; const struct GNUNET_OS_ProjectData *pd; @@ -678,9 +703,26 @@ start_helpers (const char *config_filename, NULL); GNUNET_free (fn); } + { + char *fn; + + GNUNET_asprintf (&fn, + "%s/%s", + dir, + "taler-exchange-secmod-cs"); + helpers[2] = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + fn, + "taler-exchange-secmod-cs", + "-c", config_filename, + "-L", "INFO", + NULL); + GNUNET_free (fn); + } GNUNET_free (dir); if ( (NULL == helpers[0]) || - (NULL == helpers[1]) ) + (NULL == helpers[1]) || + (NULL == helpers[2]) ) { stop_helpers (helpers); return GNUNET_SYSERR; @@ -696,7 +738,7 @@ TALER_TESTING_setup_with_exchange_cfg ( { const struct TALER_TESTING_SetupContext *setup_ctx = cls; struct GNUNET_OS_Process *exchanged; - struct GNUNET_OS_Process *helpers[2]; + struct GNUNET_OS_Process *helpers[3]; unsigned long long port; char *serve; char *base_url; -- cgit v1.2.3