diff options
Diffstat (limited to 'src/mint')
34 files changed, 9827 insertions, 0 deletions
diff --git a/src/mint/.gitignore b/src/mint/.gitignore new file mode 100644 index 000000000..a2e71d5da --- /dev/null +++ b/src/mint/.gitignore @@ -0,0 +1,6 @@ +taler-mint-dbinit +taler-mint-keycheck +taler-mint-keyup +taler-mint-pursemod +taler-mint-reservemod +taler-mint-httpd
\ No newline at end of file diff --git a/src/mint/Makefile.am b/src/mint/Makefile.am new file mode 100644 index 000000000..2ae153485 --- /dev/null +++ b/src/mint/Makefile.am @@ -0,0 +1,131 @@ +AM_CPPFLAGS = -I$(top_srcdir)/src/include $(POSTGRESQL_CPPFLAGS) + +lib_LTLIBRARIES = \ + libtalermint.la \ + libtalermintapi.la + +libtalermint_la_SOURCES = \ + mint_common.c \ + mint_db.c + +libtalermint_la_LIBADD = \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil \ + -lpq + +libtalermint_la_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) \ + -version-info 0:0:0 \ + -no-undefined + +libtalermintapi_la_SOURCES = \ + mint_api.c + +libtalermintapi_la_LIBADD = \ + -lgnunetutil \ + -ljansson \ + -lcurl + +libtalermintapi_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined + + +bin_PROGRAMS = \ + taler-mint-keyup \ + taler-mint-keycheck \ + taler-mint-reservemod \ + taler-mint-httpd \ + taler-mint-dbinit + +taler_mint_keyup_SOURCES = \ + taler-mint-keyup.c + +taler_mint_keyup_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/mint/libtalermint.la \ + -lpq \ + -lgnunetutil +taler_mint_keyup_LDFLAGS = $(POSTGRESQL_LDFLAGS) + + +taler_mint_keycheck_SOURCES = \ + taler-mint-keycheck.c + +taler_mint_keycheck_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/mint/libtalermint.la \ + -lgnunetutil \ + -lpq +taler_mint_keycheck_LDFLAGS = $(POSTGRESQL_LDFLAGS) + +taler_mint_reservemod_SOURCES = \ + taler-mint-reservemod.c +taler_mint_reservemod_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/mint/libtalermint.la \ + -lpq \ + -lgnunetutil +taler_mint_reservemod_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) + +taler_mint_httpd_SOURCES = \ + taler-mint-httpd.c \ + taler-mint-httpd_mhd.c \ + taler-mint-httpd_keys.c \ + taler-mint-httpd_deposit.c \ + taler-mint-httpd_withdraw.c \ + taler-mint-httpd_refresh.c +taler_mint_httpd_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/mint/libtalermint.la \ + -lpq \ + -lmicrohttpd \ + -ljansson \ + -lgnunetutil \ + -lpthread +taler_mint_httpd_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) + + +taler_mint_dbinit_SOURCES = \ + taler-mint-dbinit.c +taler_mint_dbinit_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/mint/libtalermint.la \ + -lpq \ + -lgnunetutil +taler_mint_dbinit_LDFLAGS = $(POSTGRESQL_LDFLAGS) + +check_PROGRAMS = \ + test-mint-api \ + test-mint-deposits \ + test-mint-common + +test_mint_api_SOURCES = test_mint_api.c +test_mint_api_LDADD = \ + libtalermintapi.la \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil \ + -ljansson + +test_mint_deposits_SOURCES = \ + test_mint_deposits.c +test_mint_deposits_LDADD = \ + libtalermint.la \ + $(top_srcdir)/src/util/libtalerutil.la \ + -lgnunetutil \ + -lpq + +test_mint_common_SOURCES = \ + test_mint_common.c +test_mint_common_LDADD = \ + libtalermint.la \ + $(top_srcdir)/src/util/libtalerutil.la \ + -lgnunetutil diff --git a/src/mint/mint.h b/src/mint/mint.h new file mode 100644 index 000000000..5adce03c6 --- /dev/null +++ b/src/mint/mint.h @@ -0,0 +1,198 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler_mint.h + * @brief Common functionality for the mint + * @author Florian Dold + * @author Benedikt Mueller + */ + +#ifndef _MINT_H +#define _MINT_H + +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_common.h> +#include <libpq-fe.h> +#include "taler_util.h" +#include "taler_rsa.h" + +#define DIR_SIGNKEYS "signkeys" +#define DIR_DENOMKEYS "denomkeys" + + +GNUNET_NETWORK_STRUCT_BEGIN + + +/** + * FIXME + */ +struct TALER_MINT_SignKeyIssue +{ + struct GNUNET_CRYPTO_EddsaPrivateKey signkey_priv; + struct GNUNET_CRYPTO_EddsaSignature signature; + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_CRYPTO_EddsaPublicKey master_pub; + struct GNUNET_TIME_AbsoluteNBO start; + struct GNUNET_TIME_AbsoluteNBO expire; + struct GNUNET_CRYPTO_EddsaPublicKey signkey_pub; +}; + +struct TALER_MINT_DenomKeyIssue +{ + /** + * The private key of the denomination. Will be NULL if the private key is + * not available. + */ + struct TALER_RSA_PrivateKey *denom_priv; + struct GNUNET_CRYPTO_EddsaSignature signature; + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_CRYPTO_EddsaPublicKey master; + struct GNUNET_TIME_AbsoluteNBO start; + struct GNUNET_TIME_AbsoluteNBO expire_withdraw; + struct GNUNET_TIME_AbsoluteNBO expire_spend; + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + struct TALER_AmountNBO value; + struct TALER_AmountNBO fee_withdraw; + struct TALER_AmountNBO fee_deposit; + struct TALER_AmountNBO fee_refresh; +}; + +struct RefreshMeltSignatureBody +{ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_HashCode melt_hash; +}; + +struct RefreshCommitSignatureBody +{ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_HashCode commit_hash; +}; + +struct RefreshCommitResponseSignatureBody +{ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + uint16_t noreveal_index; +}; + +struct RefreshMeltResponseSignatureBody +{ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_HashCode melt_response_hash; +}; + + +struct RefreshMeltConfirmSignRequestBody +{ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_CRYPTO_EddsaPublicKey session_pub; +}; + + +GNUNET_NETWORK_STRUCT_END + + + +/** + * Iterator for sign keys. + * + * @param cls closure + * @param ski the sign key issue + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +typedef int (*TALER_MINT_SignkeyIterator)(void *cls, + const struct TALER_MINT_SignKeyIssue *ski); + +/** + * Iterator for denomination keys. + * + * @param cls closure + * @param dki the denomination key issue + * @param alias coin alias + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +typedef int (*TALER_MINT_DenomkeyIterator)(void *cls, + const char *alias, + const struct TALER_MINT_DenomKeyIssue *dki); + + + +/** + * FIXME + */ +int +TALER_MINT_signkeys_iterate (const char *mint_base_dir, + TALER_MINT_SignkeyIterator it, void *cls); + + +/** + * FIXME + */ +int +TALER_MINT_denomkeys_iterate (const char *mint_base_dir, + TALER_MINT_DenomkeyIterator it, void *cls); + + +/** + * Exports a denomination key to the given file + * + * @param filename the file where to write the denomination key + * @param dki the denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure. + */ +int +TALER_MINT_write_denom_key (const char *filename, + const struct TALER_MINT_DenomKeyIssue *dki); + + +/** + * Import a denomination key from the given file + * + * @param filename the file to import the key from + * @param dki pointer to return the imported denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +int +TALER_MINT_read_denom_key (const char *filename, + struct TALER_MINT_DenomKeyIssue *dki); + + +/** + * Load the configuration for the mint in the given + * directory. + * + * @param mint_base_dir the mint's base directory + * @return the mint configuratin, or NULL on error + */ +struct GNUNET_CONFIGURATION_Handle * +TALER_MINT_config_load (const char *mint_base_dir); + + +int +TALER_TALER_DB_extract_amount (PGresult *result, unsigned int row, + int indices[3], struct TALER_Amount *denom); + +int +TALER_TALER_DB_extract_amount_nbo (PGresult *result, unsigned int row, + int indices[3], struct TALER_AmountNBO *denom_nbo); + +#endif /* _MINT_H */ + diff --git a/src/mint/mint_api.c b/src/mint/mint_api.c new file mode 100644 index 000000000..b8d42b274 --- /dev/null +++ b/src/mint/mint_api.c @@ -0,0 +1,1121 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + 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, If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint/mint_api.c + * @brief Implementation of the client interface to mint's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_mint_service.h" +#include "taler_signatures.h" +#include "mint.h" + +#define CURL_STRERROR(TYPE, FUNCTION, CODE) \ + GNUNET_log (TYPE, "cURL function `%s' has failed at `%s:%d' with error: %s", \ + FUNCTION, __FILE__, __LINE__, curl_easy_strerror (CODE)); + + + +/** + * Print JSON parsing related error information + */ +#define JSON_WARN(error) \ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \ + "JSON parsing failed at %s:%u: %s (%s)", \ + __FILE__, __LINE__, error.text, error.source) + +/** + * Failsafe flag + */ +static int fail; + +/** + * Context + */ +struct TALER_MINT_Context +{ + /** + * CURL multi handle + */ + CURLM *multi; + + /** + * CURL share handle + */ + CURLSH *share; + + /** + * Perform task handle + */ + struct GNUNET_SCHEDULER_Task *perform_task; +}; + +/** + * Type of requests we currently have + */ +enum RequestType +{ + /** + * No request + */ + REQUEST_TYPE_NONE, + + /** + * Current request is to receive mint's keys + */ + REQUEST_TYPE_KEYSGET, + + /** + * Current request is to submit a deposit permission and get its status + */ + REQUEST_TYPE_DEPOSIT +}; + + +/** + * Handle to the mint + */ +struct TALER_MINT_Handle +{ + /** + * The context of this handle + */ + struct TALER_MINT_Context *ctx; + + /** + * The hostname of the mint + */ + char *hostname; + + /** + * The CURL handle + */ + CURL *curl; + + /** + * Error buffer for CURL + */ + char emsg[CURL_ERROR_SIZE]; + + /** + * Download buffer + */ + void *buf; + + /** + * The currently active request + */ + union { + /** + * Used to denote no request if set to NULL + */ + void *none; + + /** + * Denom keys get request if REQUEST_TYPE_KEYSGET + */ + struct TALER_MINT_KeysGetHandle *keys_get; + + /** + * Deposit request if REQUEST_TYPE_DEPOSIT + */ + struct TALER_MINT_DepositHandle *deposit; + } req; + + /** + * The size of the download buffer + */ + size_t buf_size; + + /** + * Active request type + */ + enum RequestType req_type; + + /** + * The service port of the mint + */ + uint16_t port; + + /** + * Are we connected to the mint? + */ + uint8_t connected; + +}; + + +/** + * A handle to get the keys of a mint + */ +struct TALER_MINT_KeysGetHandle +{ + /** + * The connection to mint this request handle will use + */ + struct TALER_MINT_Handle *mint; + + /** + * The url for this handle + */ + char *url; + + TALER_MINT_KeysGetCallback cb; + void *cls; + + TALER_MINT_ContinuationCallback cont_cb; + void *cont_cls; +}; + + +/** + * A handle to submit a deposit permission and get its status + */ +struct TALER_MINT_DepositHandle +{ + /** + *The connection to mint this request handle will use + */ + struct TALER_MINT_Handle *mint; + + /** + * The url for this handle + */ + char *url; + + TALER_MINT_DepositResultCallback cb; + void *cls; + + char *json_enc; + + struct curl_slist *headers; + +}; + + +/** + * Parses the timestamp encoded as ASCII string as UNIX timstamp. + * + * @param abs successfully parsed timestamp will be returned thru this parameter + * @param tstamp_enc the ASCII encoding of the timestamp + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +parse_timestamp (struct GNUNET_TIME_Absolute *abs, const char *tstamp_enc) +{ + unsigned long tstamp; + + if (1 != sscanf (tstamp_enc, "%lu", &tstamp)) + return GNUNET_SYSERR; + *abs = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get_zero_ (), + GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, tstamp)); + return GNUNET_OK; +} + + +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + + +static int +parse_json_signkey (struct TALER_MINT_SigningPublicKey **_sign_key, + json_t *sign_key_obj, + struct GNUNET_CRYPTO_EddsaPublicKey *master_key) +{ + json_t *valid_from_obj; + json_t *valid_until_obj; + json_t *key_obj; + json_t *sig_obj; + const char *valid_from_enc; + const char *valid_until_enc; + const char *key_enc; + const char *sig_enc; + struct TALER_MINT_SigningPublicKey *sign_key; + struct TALER_MINT_SignKeyIssue sign_key_issue; + struct GNUNET_CRYPTO_EddsaSignature sig; + struct GNUNET_TIME_Absolute valid_from; + struct GNUNET_TIME_Absolute valid_until; + + EXITIF (JSON_OBJECT != json_typeof (sign_key_obj)); + EXITIF (NULL == (valid_from_obj = json_object_get (sign_key_obj, + "stamp_start"))); + EXITIF (NULL == (valid_until_obj = json_object_get (sign_key_obj, + "stamp_expire"))); + EXITIF (NULL == (key_obj = json_object_get (sign_key_obj, "key"))); + EXITIF (NULL == (sig_obj = json_object_get (sign_key_obj, "master_sig"))); + EXITIF (NULL == (valid_from_enc = json_string_value (valid_from_obj))); + EXITIF (NULL == (valid_until_enc = json_string_value (valid_until_obj))); + EXITIF (NULL == (key_enc = json_string_value (key_obj))); + EXITIF (NULL == (sig_enc = json_string_value (sig_obj))); + EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_from, + valid_from_enc)); + EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_until, + valid_until_enc)); + EXITIF (52 != strlen (key_enc)); /* strlen(base32(char[32])) = 52 */ + EXITIF (103 != strlen (sig_enc)); /* strlen(base32(char[64])) = 103 */ + EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (sig_enc, 103, + &sig, sizeof (sig))); + (void) memset (&sign_key_issue, 0, sizeof (sign_key_issue)); + EXITIF (GNUNET_SYSERR == + GNUNET_CRYPTO_eddsa_public_key_from_string (key_enc, + 52, + &sign_key_issue.signkey_pub)); + sign_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNKEY); + sign_key_issue.purpose.size = + htonl (sizeof (sign_key_issue) + - offsetof (struct TALER_MINT_SignKeyIssue, purpose)); + sign_key_issue.master_pub = *master_key; + sign_key_issue.start = GNUNET_TIME_absolute_hton (valid_from); + sign_key_issue.expire = GNUNET_TIME_absolute_hton (valid_until); + EXITIF (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNKEY, + &sign_key_issue.purpose, + &sig, + master_key)); + sign_key = GNUNET_new (struct TALER_MINT_SigningPublicKey); + sign_key->valid_from = valid_from; + sign_key->valid_until = valid_until; + sign_key->key = sign_key_issue.signkey_pub; + *_sign_key = sign_key; + return GNUNET_OK; + + EXITIF_exit: + return GNUNET_SYSERR; +} + + +static int +parse_json_amount (json_t *amount_obj, struct TALER_Amount *amt) +{ + json_t *obj; + const char *currency_str; + int value; + int fraction; + + EXITIF (NULL == (obj = json_object_get (amount_obj, "currency"))); + EXITIF (NULL == (currency_str = json_string_value (obj))); + EXITIF (NULL == (obj = json_object_get (amount_obj, "value"))); + EXITIF (JSON_INTEGER != json_typeof (obj)); + EXITIF (0 > (value = json_integer_value (obj))); + EXITIF (NULL == (obj = json_object_get (amount_obj, "fraction"))); + EXITIF (JSON_INTEGER != json_typeof (obj)); + EXITIF (0 > (fraction = json_integer_value (obj))); + (void) memset (amt->currency, 0, sizeof (amt->currency)); + (void) strncpy (amt->currency, currency_str, sizeof (amt->currency) - 1); + amt->value = (uint32_t) value; + amt->fraction = (uint32_t) fraction; + return GNUNET_OK; + + EXITIF_exit: + return GNUNET_SYSERR; +} + +static int +parse_json_denomkey (struct TALER_MINT_DenomPublicKey **_denom_key, + json_t *denom_key_obj, + struct GNUNET_CRYPTO_EddsaPublicKey *master_key) +{ + json_t *obj; + const char *sig_enc; + const char *deposit_valid_until_enc; + const char *withdraw_valid_until_enc; + const char *valid_from_enc; + const char *key_enc; + struct TALER_MINT_DenomPublicKey *denom_key; + struct GNUNET_TIME_Absolute valid_from; + struct GNUNET_TIME_Absolute withdraw_valid_until; + struct GNUNET_TIME_Absolute deposit_valid_until; + struct TALER_Amount value; + struct TALER_Amount fee_withdraw; + struct TALER_Amount fee_deposit; + struct TALER_Amount fee_refresh; + struct TALER_MINT_DenomKeyIssue denom_key_issue; + struct GNUNET_CRYPTO_EddsaSignature sig; + + EXITIF (JSON_OBJECT != json_typeof (denom_key_obj)); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "master_sig"))); + EXITIF (NULL == (sig_enc = json_string_value (obj))); + EXITIF (103 != strlen (sig_enc)); + EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (sig_enc, 103, + &sig, sizeof (sig))); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_expire_deposit"))); + EXITIF (NULL == (deposit_valid_until_enc = json_string_value (obj))); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_expire_withdraw"))); + EXITIF (NULL == (withdraw_valid_until_enc = json_string_value (obj))); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_start"))); + EXITIF (NULL == (valid_from_enc = json_string_value (obj))); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "denom_pub"))); + EXITIF (NULL == (key_enc = json_string_value (obj))); + EXITIF (52 != strlen (key_enc)); /* strlen(base32(char[32])) = 52 */ + EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_from, valid_from_enc)); + EXITIF (GNUNET_SYSERR == parse_timestamp (&withdraw_valid_until, + withdraw_valid_until_enc)); + EXITIF (GNUNET_SYSERR == parse_timestamp (&deposit_valid_until, + deposit_valid_until_enc)); + + (void) memset (&denom_key_issue, 0, sizeof (denom_key_issue)); + EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (key_enc, 52, + &denom_key_issue.denom_pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded))); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "value"))); + EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &value)); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_withdraw"))); + EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_withdraw)); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_deposit"))); + EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_deposit)); + EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_refresh"))); + EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_refresh)); + denom_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOM); + denom_key_issue.purpose.size = htonl + (sizeof (struct TALER_MINT_DenomKeyIssue) - + offsetof (struct TALER_MINT_DenomKeyIssue, purpose)); + denom_key_issue.master = *master_key; + denom_key_issue.start = GNUNET_TIME_absolute_hton (valid_from); + denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (withdraw_valid_until); + denom_key_issue.expire_spend = GNUNET_TIME_absolute_hton (deposit_valid_until); + denom_key_issue.value = TALER_amount_hton (value); + denom_key_issue.fee_withdraw = TALER_amount_hton (fee_withdraw); + denom_key_issue.fee_deposit = TALER_amount_hton (fee_deposit); + denom_key_issue.fee_refresh = TALER_amount_hton (fee_refresh); + EXITIF (GNUNET_SYSERR == + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOM, + &denom_key_issue.purpose, + &sig, + master_key)); + denom_key = GNUNET_new (struct TALER_MINT_DenomPublicKey); + denom_key->key = denom_key_issue.denom_pub; + denom_key->valid_from = valid_from; + denom_key->withdraw_valid_until = withdraw_valid_until; + denom_key->deposit_valid_until = deposit_valid_until; + denom_key->value = value; + denom_key->fee_withdraw = fee_withdraw; + denom_key->fee_deposit = fee_deposit; + denom_key->fee_refresh = fee_refresh; + *_denom_key = denom_key; + return GNUNET_OK; + + EXITIF_exit: + return GNUNET_SYSERR; +} + +static int +parse_response_keys_get (const char *in, size_t size, + struct TALER_MINT_SigningPublicKey ***_sign_keys, + unsigned int *_n_sign_keys, + struct TALER_MINT_DenomPublicKey ***_denom_keys, + unsigned int *_n_denom_keys) +{ + json_t *resp_obj; + struct TALER_MINT_DenomPublicKey **denom_keys; + struct GNUNET_CRYPTO_EddsaPublicKey master_key; + struct GNUNET_TIME_Absolute list_issue_date; + struct TALER_MINT_SigningPublicKey **sign_keys; + unsigned int n_denom_keys; + unsigned int n_sign_keys; + json_error_t error; + unsigned int index; + int OK; + + denom_keys = NULL; + n_denom_keys = 0; + sign_keys = NULL; + n_sign_keys = 0; + OK = 0; + resp_obj = json_loadb (in, size, + JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, + &error); + if (NULL == resp_obj) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unable to parse received data as JSON object\n"); + return GNUNET_SYSERR; + } + + EXITIF (JSON_OBJECT != json_typeof (resp_obj)); + { + /* parse the master public key */ + json_t *master_key_obj; + const char *master_key_enc; + + EXITIF (NULL == (master_key_obj = json_object_get (resp_obj, "master_pub"))); + EXITIF (NULL == (master_key_enc = json_string_value (master_key_obj))); + EXITIF (52 != strlen (master_key_enc)); /* strlen(base32(char[32])) = 52 */ + EXITIF (GNUNET_OK != + GNUNET_CRYPTO_eddsa_public_key_from_string (master_key_enc, + 52, + &master_key)); + } + { + /* parse the issue date of the response */ + json_t *list_issue_date_obj; + const char *tstamp_enc; + + EXITIF (NULL == (list_issue_date_obj = + json_object_get(resp_obj, "list_issue_date"))); + EXITIF (NULL == (tstamp_enc = json_string_value (list_issue_date_obj))); + EXITIF (GNUNET_SYSERR == parse_timestamp (&list_issue_date, tstamp_enc)); + } + { + /* parse the signing keys */ + json_t *sign_keys_array; + json_t *sign_key_obj; + + EXITIF (NULL == (sign_keys_array = + json_object_get (resp_obj, "signkeys"))); + EXITIF (JSON_ARRAY != json_typeof (sign_keys_array)); + EXITIF (0 == (n_sign_keys = json_array_size (sign_keys_array))); + sign_keys = GNUNET_malloc (sizeof (struct TALER_MINT_SigningPublicKey *) + * (n_sign_keys + 1)); + index = 0; + json_array_foreach (sign_keys_array, index, sign_key_obj) { + EXITIF (GNUNET_SYSERR == parse_json_signkey (&sign_keys[index], + sign_key_obj, + &master_key)); + } + } + { + /* parse the denomination keys */ + json_t *denom_keys_array; + json_t *denom_key_obj; + + EXITIF (NULL == (denom_keys_array = json_object_get (resp_obj, "denoms"))); + EXITIF (JSON_ARRAY != json_typeof (denom_keys_array)); + EXITIF (0 == (n_denom_keys = json_array_size (denom_keys_array))); + denom_keys = GNUNET_malloc (sizeof (struct TALER_MINT_DenomPublicKey *) + * (n_denom_keys + 1)); + index = 0; + json_array_foreach (denom_keys_array, index, denom_key_obj) { + EXITIF (GNUNET_SYSERR == parse_json_denomkey (&denom_keys[index], + denom_key_obj, + &master_key)); + } + } + OK = 1; + + EXITIF_exit: + json_decref (resp_obj); + if (!OK) + { + if (NULL != sign_keys) + { + for (index=0; NULL != sign_keys[index]; index++) + GNUNET_free_non_null (sign_keys[index]); + GNUNET_free (sign_keys); + } + if (NULL != denom_keys) + { + for (index=0; NULL != denom_keys[index]; index++) + GNUNET_free_non_null (denom_keys[index]); + GNUNET_free (denom_keys); + } + return GNUNET_SYSERR; + } + + *_sign_keys = sign_keys; + *_n_sign_keys = n_sign_keys; + *_denom_keys = denom_keys; + *_n_denom_keys = n_denom_keys; + return GNUNET_OK; +} + + +int +parse_deposit_response (void *buf, size_t size, int *r_status, json_t **r_obj) +{ + json_t *obj; + const char *status_str; + json_error_t error; + + status_str = NULL; + obj = NULL; + obj = json_loadb (buf, size, + JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error); + if (NULL == obj) + { + JSON_WARN (error); + return GNUNET_SYSERR; + } + EXITIF (-1 == json_unpack (obj, "{s:s}", "status", &status_str)); + LOG_DEBUG ("Received deposit response: %s from mint\n", status_str); + if (0 == strcmp ("DEPOSIT_OK", status_str)) + *r_status = 1; + else if (0 == strcmp ("DEPOSIT_QUEUED", status_str)) + *r_status = 2; + else + *r_status = 0; + *r_obj = obj; + + return GNUNET_OK; + EXITIF_exit: + json_decref (obj); + return GNUNET_SYSERR; +} + +#undef EXITIF + +static void +mint_connect (struct TALER_MINT_Handle *mint) +{ + struct TALER_MINT_Context *ctx = mint->ctx; + + GNUNET_assert (0 == mint->connected); + GNUNET_assert (CURLM_OK == curl_multi_add_handle (ctx->multi, mint->curl)); + mint->connected = GNUNET_YES; +} + +static void +mint_disconnect (struct TALER_MINT_Handle *mint) +{ + struct TALER_MINT_Context *ctx = mint->ctx; + + GNUNET_assert (GNUNET_YES == mint->connected); + GNUNET_break (CURLM_OK == curl_multi_remove_handle (ctx->multi, + mint->curl)); + mint->connected = GNUNET_NO; + GNUNET_free_non_null (mint->buf); + mint->buf = NULL; + mint->buf_size = 0; + mint->req_type = REQUEST_TYPE_NONE; + mint->req.none = NULL; +} + +static void +cleanup_keys_get (struct TALER_MINT_KeysGetHandle *gh) +{ + GNUNET_free (gh->url); + GNUNET_free (gh); +} + +static void +cleanup_deposit (struct TALER_MINT_DepositHandle *dh) +{ + curl_slist_free_all (dh->headers); + GNUNET_free_non_null (dh->json_enc); + GNUNET_free (dh->url); + GNUNET_free (dh); +} + +static void +request_failed (struct TALER_MINT_Handle *mint, long resp_code) +{ + switch (mint->req_type) + { + case REQUEST_TYPE_NONE: + GNUNET_assert (0); + break; + case REQUEST_TYPE_KEYSGET: + { + struct TALER_MINT_KeysGetHandle *gh = mint->req.keys_get; + TALER_MINT_ContinuationCallback cont_cb; + void *cont_cls; + GNUNET_assert (NULL != gh); + cont_cb = gh->cont_cb; + cont_cls = gh->cont_cls; + cleanup_keys_get (gh); + mint_disconnect (mint); + cont_cb (cont_cls, mint->emsg); + } + break; + case REQUEST_TYPE_DEPOSIT: + { + struct TALER_MINT_DepositHandle *dh = mint->req.deposit; + TALER_MINT_DepositResultCallback cb = dh->cb; + void *cls = dh->cls; + GNUNET_assert (NULL != dh); + cleanup_deposit (dh); + mint_disconnect (mint); + cb (cls, 0, NULL, mint->emsg); + } + break; + } +} + +static void +request_succeeded (struct TALER_MINT_Handle *mint, long resp_code) +{ + char *emsg; + + emsg = NULL; + switch (mint->req_type) + { + case REQUEST_TYPE_NONE: + GNUNET_assert (0); + break; + case REQUEST_TYPE_KEYSGET: + { + struct TALER_MINT_KeysGetHandle *gh = mint->req.keys_get; + TALER_MINT_ContinuationCallback cont_cb; + void *cont_cls; + struct TALER_MINT_SigningPublicKey **sign_keys; + struct TALER_MINT_DenomPublicKey **denom_keys; + unsigned int n_sign_keys; + unsigned int n_denom_keys; + + GNUNET_assert (NULL != gh); + cont_cb = gh->cont_cb; + cont_cls = gh->cont_cls; + if (200 == resp_code) + { + /* parse JSON object from the mint->buf which is of size mint->buf_size */ + if (GNUNET_OK == + parse_response_keys_get (mint->buf, mint->buf_size, + &sign_keys, &n_sign_keys, + &denom_keys, &n_denom_keys)) + gh->cb (gh->cls, sign_keys, denom_keys); + else + emsg = GNUNET_strdup ("Error parsing response"); + } + else + GNUNET_asprintf (&emsg, "Failed with response code: %ld", resp_code); + cleanup_keys_get (gh); + mint_disconnect (mint); + cont_cb (cont_cls, emsg); + } + break; + case REQUEST_TYPE_DEPOSIT: + { + struct TALER_MINT_DepositHandle *dh = mint->req.deposit; + TALER_MINT_DepositResultCallback cb; + void *cls; + int status; + json_t *obj; + + GNUNET_assert (NULL != dh); + obj = NULL; + cb = dh->cb; + cls = dh->cls; + status = 0; + if (200 == resp_code) + { + /* parse JSON object from the mint->buf which is of size mint->buf_size */ + if (GNUNET_OK != + parse_deposit_response (mint->buf, mint->buf_size, + &status, &obj)) + emsg = GNUNET_strdup ("Error parsing response"); + } + else + GNUNET_asprintf (&emsg, "Failed with response code: %ld", resp_code); + cleanup_deposit (dh); + mint_disconnect (mint); + cb (cls, status, obj, emsg); + } + break; + } + GNUNET_free_non_null (emsg); +} + + +static void +do_perform (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc); + +static void +perform (struct TALER_MINT_Context *ctx) +{ + fd_set fd_rs; + fd_set fd_ws; + struct GNUNET_NETWORK_FDSet rs; + struct GNUNET_NETWORK_FDSet ws; + CURLMsg *cmsg; + struct TALER_MINT_Handle *mint; + long timeout; + long resp_code; + static unsigned int n_old; + int n_running; + int n_completed; + int max_fd; + + n_completed = 0; + curl_multi_perform (ctx->multi, &n_running); + GNUNET_assert (0 <= n_running); + if ((0 == n_running) || (n_running < n_old)) + { + /* some requests were completed -- handle them */ + while (NULL != (cmsg = curl_multi_info_read (ctx->multi, &n_completed))) + { + GNUNET_break (CURLMSG_DONE == cmsg->msg); /* curl only has CURLMSG_DONE */ + GNUNET_assert (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle, + CURLINFO_PRIVATE, + (char *) &mint)); + GNUNET_assert (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle, + CURLINFO_RESPONSE_CODE, + &resp_code)); + GNUNET_assert (ctx == mint->ctx); /* did we get the correct one? */ + if (CURLE_OK == cmsg->data.result) + request_succeeded (mint, resp_code); + else + request_failed (mint, resp_code); + } + } + n_old = n_running; + /* reschedule perform() */ + if (0 != n_old) + { + FD_ZERO (&fd_rs); + FD_ZERO (&fd_ws); + GNUNET_assert (CURLM_OK == curl_multi_fdset (ctx->multi, + &fd_rs, + &fd_ws, + NULL, + &max_fd)); + if (-1 == max_fd) + { + ctx->perform_task = GNUNET_SCHEDULER_add_delayed + (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100), + &do_perform, ctx); + return; + } + GNUNET_assert (CURLM_OK == curl_multi_timeout (ctx->multi, &timeout)); + if (-1 == timeout) + { + timeout = 1000 * 60 * 5; + } + GNUNET_NETWORK_fdset_zero (&rs); + GNUNET_NETWORK_fdset_zero (&ws); + GNUNET_NETWORK_fdset_copy_native (&rs, &fd_rs, max_fd + 1); + GNUNET_NETWORK_fdset_copy_native (&ws, &fd_ws, max_fd + 1); + ctx->perform_task = GNUNET_SCHEDULER_add_select + (GNUNET_SCHEDULER_PRIORITY_KEEP, + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, timeout), + &rs, &ws, + &do_perform, ctx); + } +} + + +static void +do_perform (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct TALER_MINT_Context *ctx = cls; + + GNUNET_assert (NULL != ctx->perform_task); + ctx->perform_task = NULL; + perform (ctx); +} + +static void +perform_now (struct TALER_MINT_Context *ctx) +{ + if (NULL != ctx->perform_task) + { + GNUNET_SCHEDULER_cancel (ctx->perform_task); + ctx->perform_task = NULL; + } + ctx->perform_task = GNUNET_SCHEDULER_add_now (&do_perform, ctx); +} + + +/* This function gets called by libcurl as soon as there is data received that */ +/* needs to be saved. The size of the data pointed to by ptr is size */ +/* multiplied with nmemb, it will not be zero terminated. Return the number */ +/* of bytes actually taken care of. If that amount differs from the amount passed */ +/* to your function, it'll signal an error to the library. This will abort the */ +/* transfer and return CURLE_WRITE_ERROR. */ + +/* From 7.18.0, the function can return CURL_WRITEFUNC_PAUSE which then will */ +/* cause writing to this connection to become paused. See */ +/* curl_easy_pause(3) for further details. */ + +/* This function may be called with zero bytes data if the transferred file is */ +/* empty. */ + +/* Set this option to NULL to get the internal default function. The internal */ +/* default function will write the data to the FILE * given with */ +/* CURLOPT_WRITEDATA. */ + +/* Set the userdata argument with the CURLOPT_WRITEDATA option. */ + +/* The callback function will be passed as much data as possible in all invokes, */ +/* but you cannot possibly make any assumptions. It may be one byte, it may be */ +/* thousands. The maximum amount of body data that can be passed to the write */ +/* callback is defined in the curl.h header file: CURL_MAX_WRITE_SIZE (the usual */ +/* default is 16K). If you however have CURLOPT_HEADER set, which sends */ +/* header data to the write callback, you can get up to */ +/* CURL_MAX_HTTP_HEADER bytes of header data passed into it. This usually */ +/* means 100K. */ +static size_t +download (char *bufptr, size_t size, size_t nitems, void *cls) +{ + struct TALER_MINT_Handle *mint = cls; + size_t msize; + void *buf; + + if (0 == size * nitems) + { + /* file is empty */ + return 0; + } + msize = size * nitems; + mint->buf = GNUNET_realloc (mint->buf, mint->buf_size + msize); + buf = mint->buf + mint->buf_size; + memcpy (buf, bufptr, msize); + mint->buf_size += msize; + return msize; +} + + +/** + * Initialise a connection to the mint. + * + * @param ctx the context + * @param hostname the hostname of the mint + * @param port the point where the mint's HTTP service is running. + * @param mint_key the public key of the mint. This is used to verify the + * responses of the mint. + * @return the mint handle; NULL upon error + */ +struct TALER_MINT_Handle * +TALER_MINT_connect (struct TALER_MINT_Context *ctx, + const char *hostname, + uint16_t port, + struct GNUNET_CRYPTO_EddsaPublicKey *mint_key) +{ + struct TALER_MINT_Handle *mint; + + mint = GNUNET_new (struct TALER_MINT_Handle); + mint->ctx = ctx; + mint->hostname = GNUNET_strdup (hostname); + mint->port = (0 != port) ? port : 80; + mint->curl = curl_easy_init (); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_SHARE, ctx->share)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_ERRORBUFFER, mint->emsg)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_WRITEFUNCTION, &download)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_WRITEDATA, mint)); + GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_PRIVATE, mint)); + return mint; +} + +/** + * Disconnect from the mint + * + * @param mint the mint handle + */ +void +TALER_MINT_disconnect (struct TALER_MINT_Handle *mint) +{ + if (GNUNET_YES == mint->connected) + mint_disconnect (mint); + curl_easy_cleanup (mint->curl); + GNUNET_free (mint->hostname); + GNUNET_free (mint); +} + +/** + * Get the signing and denomination key of the mint. + * + * @param mint handle to the mint + * @param cb the callback to call with each retrieved denomination key + * @param cls closure for the above callback + * @param cont_cb the callback to call after completing this asynchronous call + * @param cont_cls the closure for the continuation callback + * @return a handle to this asynchronous call; NULL upon eror + */ +struct TALER_MINT_KeysGetHandle * +TALER_MINT_keys_get (struct TALER_MINT_Handle *mint, + TALER_MINT_KeysGetCallback cb, void *cls, + TALER_MINT_ContinuationCallback cont_cb, void *cont_cls) +{ + struct TALER_MINT_KeysGetHandle *gh; + + GNUNET_assert (REQUEST_TYPE_NONE == mint->req_type); + gh = GNUNET_new (struct TALER_MINT_KeysGetHandle); + gh->mint = mint; + mint->req_type = REQUEST_TYPE_KEYSGET; + mint->req.keys_get = gh; + gh->cb = cb; + gh->cls = cls; + gh->cont_cb = cont_cb; + gh->cont_cls = cont_cls; + GNUNET_asprintf (&gh->url, "http://%s:%hu/keys", mint->hostname, mint->port); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_URL, gh->url)); + if (GNUNET_NO == mint->connected) + mint_connect (mint); + perform_now (mint->ctx); + return gh; +} + + +/** + * Cancel the asynchronous call initiated by TALER_MINT_keys_get(). This + * should not be called if either of the @a TALER_MINT_KeysGetCallback or + * @a TALER_MINT_ContinuationCallback passed to TALER_MINT_keys_get() have + * been called. + * + * @param get the handle for retrieving the keys + */ +void +TALER_MINT_keys_get_cancel (struct TALER_MINT_KeysGetHandle *get) +{ + struct TALER_MINT_Handle *mint = get->mint; + + mint_disconnect (mint); + cleanup_keys_get (get); +} + +/** + * Submit a deposit permission to the mint and get the mint's response + * + * @param mint the mint handle + * @param cb the callback to call when a reply for this request is available + * @param cls closure for the above callback + * @param deposit_obj the deposit permission received from the customer along + * with the wireformat JSON object + * @return a handle for this request; NULL if the JSON object could not be + * parsed or is of incorrect format or any other error. In this case, + * the callback is not called. + */ +struct TALER_MINT_DepositHandle * +TALER_MINT_deposit_submit_json (struct TALER_MINT_Handle *mint, + TALER_MINT_DepositResultCallback cb, + void *cls, + json_t *deposit_obj) +{ + struct TALER_MINT_DepositHandle *dh; + + GNUNET_assert (REQUEST_TYPE_NONE == mint->req_type); + dh = GNUNET_new (struct TALER_MINT_DepositHandle); + dh->mint = mint; + mint->req_type = REQUEST_TYPE_DEPOSIT; + mint->req.deposit = dh; + dh->cb = cb; + dh->cls = cls; + GNUNET_asprintf (&dh->url, "http://%s:%hu/deposit", mint->hostname, mint->port); + GNUNET_assert (NULL != (dh->json_enc = json_dumps (deposit_obj, JSON_COMPACT))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_URL, dh->url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_POSTFIELDS, + dh->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_POSTFIELDSIZE, + strlen (dh->json_enc))); + GNUNET_assert (NULL != (dh->headers = + curl_slist_append (dh->headers, "Content-Type: application/json"))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (mint->curl, CURLOPT_HTTPHEADER, dh->headers)); + if (GNUNET_NO == mint->connected) + mint_connect (mint); + perform_now (mint->ctx); + return dh; +} + + +/** + * Cancel a deposit permission request. This function cannot be used on a + * request handle if a response is already served for it. + * + * @param the deposit permission request handle + */ +void +TALER_MINT_deposit_submit_cancel (struct TALER_MINT_DepositHandle *deposit) +{ + struct TALER_MINT_Handle *mint = deposit->mint; + + mint_disconnect (mint); + cleanup_deposit (deposit); +} + + +/** + * Initialise this library. This function should be called before using any of + * the following functions. + * + * @return library context + */ +struct TALER_MINT_Context * +TALER_MINT_init () +{ + struct TALER_MINT_Context *ctx; + CURLM *multi; + CURLSH *share; + + if (fail) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "cURL was not initialised properly\n"); + return NULL; + } + if (NULL == (multi = curl_multi_init ())) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot create a cURL multi handle\n"); + return NULL; + } + if (NULL == (share = curl_share_init ())) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot create a cURL share handle\n"); + return NULL; + } + ctx = GNUNET_new (struct TALER_MINT_Context); + ctx->multi = multi; + ctx->share = share; + return ctx; +} + + +/** + * Cleanup library initialisation resources. This function should be called + * after using this library to cleanup the resources occupied during library's + * initialisation. + * + * @param ctx the library context + */ +void +TALER_MINT_cleanup (struct TALER_MINT_Context *ctx) +{ + curl_share_cleanup (ctx->share); + curl_multi_cleanup (ctx->multi); + if (NULL != ctx->perform_task) + { + GNUNET_break (0); /* investigate why this happens */ + GNUNET_SCHEDULER_cancel (ctx->perform_task); + } + GNUNET_free (ctx); +} + + +__attribute__ ((constructor)) +void +TALER_MINT_constructor__ (void) +{ + CURLcode ret; + if (CURLE_OK != (ret = curl_global_init (CURL_GLOBAL_DEFAULT))) + { + CURL_STRERROR (GNUNET_ERROR_TYPE_ERROR, "curl_global_init", ret); + fail = 1; + } +} + +__attribute__ ((destructor)) +void +TALER_MINT_destructor__ (void) +{ + if (fail) + return; + curl_global_cleanup (); +} diff --git a/src/mint/mint_common.c b/src/mint/mint_common.c new file mode 100644 index 000000000..4afbf072b --- /dev/null +++ b/src/mint/mint_common.c @@ -0,0 +1,283 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint_common.c + * @brief Common functionality for the mint + * @author Florian Dold + * @author Benedikt Mueller + * @author Sree Harsha Totakura + */ + +#include "platform.h" +#include "mint.h" + +struct SignkeysIterateContext +{ + TALER_MINT_SignkeyIterator it; + void *it_cls; +}; + + +struct DenomkeysIterateContext +{ + const char *alias; + TALER_MINT_DenomkeyIterator it; + void *it_cls; +}; + + +static int +signkeys_iterate_dir_iter (void *cls, + const char *filename) +{ + + struct SignkeysIterateContext *skc = cls; + ssize_t nread; + struct TALER_MINT_SignKeyIssue issue; + nread = GNUNET_DISK_fn_read (filename, + &issue, + sizeof (struct TALER_MINT_SignKeyIssue)); + if (nread != sizeof (struct TALER_MINT_SignKeyIssue)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Invalid signkey file: '%s'\n", filename); + return GNUNET_OK; + } + return skc->it (skc->it_cls, &issue); +} + + +int +TALER_MINT_signkeys_iterate (const char *mint_base_dir, + TALER_MINT_SignkeyIterator it, void *cls) +{ + char *signkey_dir; + size_t len; + struct SignkeysIterateContext skc; + + len = GNUNET_asprintf (&signkey_dir, ("%s" DIR_SEPARATOR_STR DIR_SIGNKEYS), mint_base_dir); + GNUNET_assert (len > 0); + + skc.it = it; + skc.it_cls = cls; + + return GNUNET_DISK_directory_scan (signkey_dir, &signkeys_iterate_dir_iter, &skc); +} + + +/** + * Import a denomination key from the given file + * + * @param filename the file to import the key from + * @param dki pointer to return the imported denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +int +TALER_MINT_read_denom_key (const char *filename, + struct TALER_MINT_DenomKeyIssue *dki) +{ + uint64_t size; + size_t offset; + void *data; + struct TALER_RSA_PrivateKey *priv; + int ret; + + ret = GNUNET_SYSERR; + data = NULL; + offset = sizeof (struct TALER_MINT_DenomKeyIssue) + - offsetof (struct TALER_MINT_DenomKeyIssue, signature); + if (GNUNET_OK != GNUNET_DISK_file_size (filename, + &size, + GNUNET_YES, + GNUNET_YES)) + goto cleanup; + if (size <= offset) + { + GNUNET_break (0); + goto cleanup; + } + data = GNUNET_malloc (size); + if (size != GNUNET_DISK_fn_read (filename, + data, + size)) + goto cleanup; + if (NULL == (priv = TALER_RSA_decode_key (data + offset, size - offset))) + goto cleanup; + dki->denom_priv = priv; + (void) memcpy (&dki->signature, data, offset); + ret = GNUNET_OK; + + cleanup: + GNUNET_free_non_null (data); + return ret; +} + + +/** + * Exports a denomination key to the given file + * + * @param filename the file where to write the denomination key + * @param dki the denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure. + */ +int +TALER_MINT_write_denom_key (const char *filename, + const struct TALER_MINT_DenomKeyIssue *dki) +{ + struct TALER_RSA_PrivateKeyBinaryEncoded *priv_enc; + struct GNUNET_DISK_FileHandle *fh; + ssize_t wrote; + size_t wsize; + int ret; + + fh = NULL; + priv_enc = NULL; + ret = GNUNET_SYSERR; + if (NULL == (fh = GNUNET_DISK_file_open + (filename, + GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_TRUNCATE, + GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE))) + goto cleanup; + if (NULL == (priv_enc = TALER_RSA_encode_key (dki->denom_priv))) + goto cleanup; + wsize = sizeof (struct TALER_MINT_DenomKeyIssue) + - offsetof (struct TALER_MINT_DenomKeyIssue, signature); + if (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh, + &dki->signature, + wsize))) + goto cleanup; + if (wrote != wsize) + goto cleanup; + wsize = ntohs (priv_enc->len); + if (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh, + priv_enc, + wsize))) + goto cleanup; + if (wrote != wsize) + goto cleanup; + ret = GNUNET_OK; + cleanup: + GNUNET_free_non_null (priv_enc); + if (NULL != fh) + (void) GNUNET_DISK_file_close (fh); + return ret; +} + + +static int +denomkeys_iterate_keydir_iter (void *cls, + const char *filename) +{ + + struct DenomkeysIterateContext *dic = cls; + struct TALER_MINT_DenomKeyIssue issue; + + if (GNUNET_OK != TALER_MINT_read_denom_key (filename, &issue)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Invalid denomkey file: '%s'\n", filename); + return GNUNET_OK; + } + return dic->it (dic->it_cls, dic->alias, &issue); +} + + +static int +denomkeys_iterate_topdir_iter (void *cls, + const char *filename) +{ + + struct DenomkeysIterateContext *dic = cls; + dic->alias = GNUNET_STRINGS_get_short_name (filename); + + // FIXME: differentiate between error case and normal iteration abortion + if (0 > GNUNET_DISK_directory_scan (filename, &denomkeys_iterate_keydir_iter, dic)) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +int +TALER_MINT_denomkeys_iterate (const char *mint_base_dir, + TALER_MINT_DenomkeyIterator it, void *cls) +{ + char *dir; + size_t len; + struct DenomkeysIterateContext dic; + len = GNUNET_asprintf (&dir, ("%s" DIR_SEPARATOR_STR DIR_DENOMKEYS), + mint_base_dir); + GNUNET_assert (len > 0); + + dic.it = it; + dic.it_cls = cls; + + // scan over alias dirs + return GNUNET_DISK_directory_scan (dir, &denomkeys_iterate_topdir_iter, &dic); +} + + +struct GNUNET_CONFIGURATION_Handle * +TALER_MINT_config_load (const char *mint_base_dir) +{ + struct GNUNET_CONFIGURATION_Handle *cfg; + char *cfg_dir; + int res; + + res = GNUNET_asprintf (&cfg_dir, "%s" DIR_SEPARATOR_STR "config", mint_base_dir); + GNUNET_assert (res > 0); + + cfg = GNUNET_CONFIGURATION_create (); + res = GNUNET_CONFIGURATION_load_from (cfg, cfg_dir); + GNUNET_free (cfg_dir); + if (GNUNET_OK != res) + return NULL; + return cfg; +} + +int +TALER_TALER_DB_extract_amount_nbo (PGresult *result, unsigned int row, + int indices[3], struct TALER_AmountNBO *denom_nbo) +{ + if ((indices[0] < 0) || (indices[1] < 0) || (indices[2] < 0)) + return GNUNET_NO; + if (sizeof (uint32_t) != PQgetlength (result, row, indices[0])) + return GNUNET_SYSERR; + if (sizeof (uint32_t) != PQgetlength (result, row, indices[1])) + return GNUNET_SYSERR; + if (PQgetlength (result, row, indices[2]) > TALER_CURRENCY_LEN) + return GNUNET_SYSERR; + denom_nbo->value = *(uint32_t *) PQgetvalue (result, row, indices[0]); + denom_nbo->fraction = *(uint32_t *) PQgetvalue (result, row, indices[1]); + memset (denom_nbo->currency, 0, TALER_CURRENCY_LEN); + memcpy (denom_nbo->currency, PQgetvalue (result, row, indices[2]), PQgetlength (result, row, indices[2])); + return GNUNET_OK; +} + + +int +TALER_TALER_DB_extract_amount (PGresult *result, unsigned int row, + int indices[3], struct TALER_Amount *denom) +{ + struct TALER_AmountNBO denom_nbo; + int res; + + res = TALER_TALER_DB_extract_amount_nbo (result, row, indices, &denom_nbo); + if (GNUNET_OK != res) + return res; + *denom = TALER_amount_ntoh (denom_nbo); + return GNUNET_OK; +} + +/* end of mint_common.c */ diff --git a/src/mint/mint_db.c b/src/mint/mint_db.c new file mode 100644 index 000000000..6dc025877 --- /dev/null +++ b/src/mint/mint_db.c @@ -0,0 +1,1838 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint_db.c + * @brief Database access for the mint + * @author Florian Dold + */ +#include "platform.h" +#include "taler_db_lib.h" +#include "taler_signatures.h" +#include "mint_db.h" +#include "mint.h" +#include <pthread.h> + +/** + * Thread-local database connection. + * Contains a pointer to PGconn or NULL. + */ +static pthread_key_t db_conn_threadlocal; + + +/** + * Database connection string, as read from + * the configuration. + */ +static char *TALER_MINT_db_connection_cfg_str; + + +#define break_db_err(result) do { \ + GNUNET_break(0); \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \ + } while (0) + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + +int +TALER_MINT_DB_get_collectable_blindcoin (PGconn *db_conn, + struct TALER_RSA_BlindedSignaturePurpose *blind_ev, + struct CollectableBlindcoin *collectable) +{ + PGresult *result; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (blind_ev), + TALER_DB_QUERY_PARAM_END + }; + result = TALER_DB_exec_prepared (db_conn, "get_collectable_blindcoins", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("blind_ev_sig", &collectable->ev_sig), + TALER_DB_RESULT_SPEC("denom_pub", &collectable->denom_pub), + TALER_DB_RESULT_SPEC("reserve_sig", &collectable->reserve_sig), + TALER_DB_RESULT_SPEC("reserve_pub", &collectable->reserve_pub), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + (void) memcpy (&collectable->ev, blind_ev, sizeof (struct TALER_RSA_BlindedSignaturePurpose)); + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_insert_collectable_blindcoin (PGconn *db_conn, + const struct CollectableBlindcoin *collectable) +{ + PGresult *result; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (&collectable->ev), + TALER_DB_QUERY_PARAM_PTR (&collectable->ev_sig), + TALER_DB_QUERY_PARAM_PTR (&collectable->denom_pub), + TALER_DB_QUERY_PARAM_PTR (&collectable->reserve_pub), + TALER_DB_QUERY_PARAM_PTR (&collectable->reserve_sig), + TALER_DB_QUERY_PARAM_END + }; + result = TALER_DB_exec_prepared (db_conn, "insert_collectable_blindcoins", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Insert failed (updated '%s' tupes instead of '1')\n", + PQcmdTuples (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_get_reserve (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub, + struct Reserve *reserve) +{ + PGresult *result; + int res; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (reserve_pub), + TALER_DB_QUERY_PARAM_END + }; + + result = TALER_DB_exec_prepared (db_conn, "get_reserve", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + reserve->reserve_pub = *reserve_pub; + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("status_sig", &reserve->status_sig), + TALER_DB_RESULT_SPEC("status_sign_pub", &reserve->status_sign_pub), + TALER_DB_RESULT_SPEC_END + }; + + res = TALER_DB_extract_result (result, rs, 0); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + { + int fnums[] = { + PQfnumber (result, "balance_value"), + PQfnumber (result, "balance_fraction"), + PQfnumber (result, "balance_currency"), + }; + if (GNUNET_OK != TALER_TALER_DB_extract_amount_nbo (result, 0, fnums, &reserve->balance)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + } + + /* FIXME: Add expiration?? */ + + PQclear (result); + return GNUNET_OK; +} + + +/* If fresh is GNUNET_YES, set some fields to NULL as they are not actually valid */ +int +TALER_MINT_DB_update_reserve (PGconn *db_conn, + const struct Reserve *reserve, + int fresh) +{ + PGresult *result; + uint64_t stamp_sec; + + stamp_sec = GNUNET_ntohll (GNUNET_TIME_absolute_ntoh (reserve->expiration).abs_value_us / 1000000); + + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (&reserve->reserve_pub), + TALER_DB_QUERY_PARAM_PTR (&reserve->balance.value), + TALER_DB_QUERY_PARAM_PTR (&reserve->balance.fraction), + TALER_DB_QUERY_PARAM_PTR_SIZED (&reserve->balance.currency, + strlen (reserve->balance.currency)), + TALER_DB_QUERY_PARAM_PTR (&reserve->status_sig), + TALER_DB_QUERY_PARAM_PTR (&reserve->status_sign_pub), + TALER_DB_QUERY_PARAM_PTR (&stamp_sec), + TALER_DB_QUERY_PARAM_END + }; + + /* set some fields to NULL if they are not actually valid */ + + if (GNUNET_YES == fresh) + { + unsigned i; + for (i = 4; i <= 7; i += 1) + { + params[i].data = NULL; + params[i].size = 0; + } + } + + result = TALER_DB_exec_prepared (db_conn, "update_reserve", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Update failed (updated '%s' tupes instead of '1')\n", + PQcmdTuples (result)); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + + +int +TALER_MINT_DB_prepare (PGconn *db_conn) +{ + PGresult *result; + + result = PQprepare (db_conn, "get_reserve", + "SELECT " + " balance_value, balance_fraction, balance_currency " + ",expiration_date, blind_session_pub, blind_session_priv" + ",status_sig, status_sign_pub " + "FROM reserves " + "WHERE reserve_pub=$1 " + "LIMIT 1; ", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "update_reserve", + "UPDATE reserves " + "SET" + " balance_value=$2 " + ",balance_fraction=$3 " + ",balance_currency=$4 " + ",status_sig=$5 " + ",status_sign_pub=$6 " + ",expiration_date=$7 " + "WHERE reserve_pub=$1 ", + 9, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + result = PQprepare (db_conn, "insert_collectable_blindcoins", + "INSERT INTO collectable_blindcoins ( " + " blind_ev, blind_ev_sig " + ",denom_pub, reserve_pub, reserve_sig) " + "VALUES ($1, $2, $3, $4, $5)", + 6, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_collectable_blindcoins", + "SELECT " + "blind_ev_sig, denom_pub, reserve_sig, reserve_pub " + "FROM collectable_blindcoins " + "WHERE blind_ev = $1", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_reserve_order", + "SELECT " + " blind_ev, blind_ev_sig, denom_pub, reserve_sig, reserve_pub " + "FROM collectable_blindcoins " + "WHERE blind_session_pub = $1", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + /* FIXME: does it make sense to store these computed values in the DB? */ + result = PQprepare (db_conn, "get_refresh_session", + "SELECT " + " (SELECT count(*) FROM refresh_melt WHERE session_pub = $1)::INT2 as num_oldcoins " + ",(SELECT count(*) FROM refresh_blind_session_keys " + " WHERE session_pub = $1 and cnc_index = 0)::INT2 as num_newcoins " + ",(SELECT count(*) FROM refresh_blind_session_keys " + " WHERE session_pub = $1 and newcoin_index = 0)::INT2 as kappa " + ",noreveal_index" + ",session_commit_sig " + ",reveal_ok " + "FROM refresh_sessions " + "WHERE session_pub = $1", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_known_coin", + "SELECT " + " coin_pub, denom_pub, denom_sig " + ",expended_value, expended_fraction, expended_currency " + ",refresh_session_pub " + "FROM known_coins " + "WHERE coin_pub = $1", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "update_known_coin", + "UPDATE known_coins " + "SET " + " denom_pub = $2 " + ",denom_sig = $3 " + ",expended_value = $4 " + ",expended_fraction = $5 " + ",expended_currency = $6 " + ",refresh_session_pub = $7 " + "WHERE " + " coin_pub = $1 ", + 7, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_known_coin", + "INSERT INTO known_coins (" + " coin_pub" + ",denom_pub" + ",denom_sig" + ",expended_value" + ",expended_fraction" + ",expended_currency" + ",refresh_session_pub" + ")" + "VALUES ($1,$2,$3,$4,$5,$6,$7)", + 7, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_refresh_commit_link", + "SELECT " + " transfer_pub " + ",link_secret_enc " + "FROM refresh_commit_link " + "WHERE session_pub = $1 AND cnc_index = $2 AND oldcoin_index = $3", + 3, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_refresh_commit_coin", + "SELECT " + " link_vector_enc " + ",coin_ev " + "FROM refresh_commit_coin " + "WHERE session_pub = $1 AND cnc_index = $2 AND newcoin_index = $3", + 3, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_refresh_order", + "INSERT INTO refresh_order ( " + " newcoin_index " + ",session_pub " + ",denom_pub " + ") " + "VALUES ($1, $2, $3) ", + 3, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_refresh_melt", + "INSERT INTO refresh_melt ( " + " session_pub " + ",oldcoin_index " + ",coin_pub " + ",denom_pub " + ") " + "VALUES ($1, $2, $3, $4) ", + 3, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_refresh_order", + "SELECT denom_pub " + "FROM refresh_order " + "WHERE session_pub = $1 AND newcoin_index = $2", + 2, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_refresh_collectable", + "SELECT ev_sig " + "FROM refresh_collectable " + "WHERE session_pub = $1 AND newcoin_index = $2", + 2, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_refresh_melt", + "SELECT coin_pub " + "FROM refresh_melt " + "WHERE session_pub = $1 AND oldcoin_index = $2", + 2, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_refresh_session", + "INSERT INTO refresh_sessions ( " + " session_pub " + ",noreveal_index " + ") " + "VALUES ($1, $2) ", + 2, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_refresh_commit_link", + "INSERT INTO refresh_commit_link ( " + " session_pub " + ",transfer_pub " + ",cnc_index " + ",oldcoin_index " + ",link_secret_enc " + ") " + "VALUES ($1, $2, $3, $4, $5) ", + 5, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_refresh_commit_coin", + "INSERT INTO refresh_commit_coin ( " + " session_pub " + ",coin_ev " + ",cnc_index " + ",newcoin_index " + ",link_vector_enc " + ") " + "VALUES ($1, $2, $3, $4, $5) ", + 5, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "insert_refresh_collectable", + "INSERT INTO refresh_collectable ( " + " session_pub " + ",newcoin_index " + ",ev_sig " + ") " + "VALUES ($1, $2, $3) ", + 3, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "set_reveal_ok", + "UPDATE refresh_sessions " + "SET reveal_ok = TRUE " + "WHERE session_pub = $1 ", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_link", + "SELECT link_vector_enc, ro.denom_pub, ev_sig " + "FROM refresh_melt rm " + " JOIN refresh_order ro USING (session_pub) " + " JOIN refresh_commit_coin rcc USING (session_pub) " + " JOIN refresh_sessions rs USING (session_pub) " + " JOIN refresh_collectable rc USING (session_pub) " + "WHERE rm.coin_pub = $1 " + "AND ro.newcoin_index = rcc.newcoin_index " + "AND ro.newcoin_index = rc.newcoin_index " + "AND rcc.cnc_index = rs.noreveal_index % ( " + " SELECT count(*) FROM refresh_commit_coin rcc2 " + " WHERE rcc2.newcoin_index = 0 AND rcc2.session_pub = rs.session_pub " + " ) ", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQprepare (db_conn, "get_transfer", + "SELECT transfer_pub, link_secret_enc " + "FROM refresh_melt rm " + " JOIN refresh_commit_link rcl USING (session_pub) " + " JOIN refresh_sessions rs USING (session_pub) " + "WHERE rm.coin_pub = $1 " + "AND rm.oldcoin_index = rcl.oldcoin_index " + "AND rcl.cnc_index = rs.noreveal_index % ( " + " SELECT count(*) FROM refresh_commit_coin rcc2 " + " WHERE newcoin_index = 0 AND rcc2.session_pub = rm.session_pub " + " ) ", + 1, NULL); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + + if (GNUNET_OK != TALER_MINT_DB_prepare_deposits (db_conn)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Roll back the current transaction of a database connection. + * + * @param db_conn the database connection + * @return GNUNET_OK on success + */ +int +TALER_MINT_DB_rollback (PGconn *db_conn) +{ + PGresult *result; + + result = PQexec(db_conn, "ROLLBACK"); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Roll back the current transaction of a database connection. + * + * @param db_conn the database connection + * @return GNUNET_OK on success + */ +int +TALER_MINT_DB_commit (PGconn *db_conn) +{ + PGresult *result; + + result = PQexec(db_conn, "COMMIT"); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Start a transaction. + * + * @param db_conn the database connection + * @return GNUNET_OK on success + */ +int +TALER_MINT_DB_transaction (PGconn *db_conn) +{ + PGresult *result; + + result = PQexec(db_conn, "BEGIN"); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Can't start transaction: %s\n", PQresultErrorMessage (result)); + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Insert a refresh order into the database. + */ +int +TALER_MINT_DB_insert_refresh_order (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub) +{ + uint16_t newcoin_index_nbo = htons (newcoin_index); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(denom_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_order", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_get_refresh_session (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + struct RefreshSession *session) +{ + int res; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_session", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + return GNUNET_NO; + + GNUNET_assert (1 == PQntuples (result)); + + /* We're done if the caller is only interested in + * whether the session exists or not */ + + if (NULL == session) + return GNUNET_YES; + + memset (session, 0, sizeof (struct RefreshSession)); + + session->session_pub = *refresh_session_pub; + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("num_oldcoins", &session->num_oldcoins), + TALER_DB_RESULT_SPEC("num_newcoins", &session->num_newcoins), + TALER_DB_RESULT_SPEC("kappa", &session->kappa), + TALER_DB_RESULT_SPEC("noreveal_index", &session->noreveal_index), + TALER_DB_RESULT_SPEC("reveal_ok", &session->reveal_ok), + TALER_DB_RESULT_SPEC_END + }; + + res = TALER_DB_extract_result (result, rs, 0); + + if (GNUNET_OK != res) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + if (TALER_DB_field_isnull (result, 0, "session_commit_sig")) + session->has_commit_sig = GNUNET_NO; + else + session->has_commit_sig = GNUNET_YES; + + session->num_oldcoins = ntohs (session->num_oldcoins); + session->num_newcoins = ntohs (session->num_newcoins); + session->kappa = ntohs (session->kappa); + session->noreveal_index = ntohs (session->noreveal_index); + + PQclear (result); + return GNUNET_YES; +} + + +int +TALER_MINT_DB_get_known_coin (PGconn *db_conn, struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + struct KnownCoin *known_coin) +{ + int res; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(coin_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_known_coin", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + return GNUNET_NO; + + GNUNET_assert (1 == PQntuples (result)); + + /* extract basic information about the known coin */ + + { + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("coin_pub", &known_coin->public_info.coin_pub), + TALER_DB_RESULT_SPEC("denom_pub", &known_coin->public_info.denom_pub), + TALER_DB_RESULT_SPEC("denom_sig", &known_coin->public_info.denom_sig), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != (res = TALER_DB_extract_result (result, rs, 0))) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + } + + /* extract the expended amount of the coin */ + + if (GNUNET_OK != TALER_DB_extract_amount (result, 0, + "expended_value", + "expended_fraction", + "expended_currency", + &known_coin->expended_balance)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + /* extract the refresh session of the coin or mark it as missing */ + + { + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("refresh_session_pub", &known_coin->refresh_session_pub), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_SYSERR == (res = TALER_DB_extract_result (result, rs, 0))) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + if (GNUNET_NO == res) + { + known_coin->is_refreshed = GNUNET_NO; + memset (&known_coin->refresh_session_pub, 0, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + } + else + { + known_coin->is_refreshed = GNUNET_YES; + } + } + + PQclear (result); + return GNUNET_YES; +} + + +int +TALER_MINT_DB_create_refresh_session (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub) +{ + uint16_t noreveal_index; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&noreveal_index), + TALER_DB_QUERY_PARAM_END + }; + + noreveal_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 1<<15); + noreveal_index = htonl (noreveal_index); + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_session", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_set_commit_signature (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const struct GNUNET_CRYPTO_EddsaSignature *commit_sig) +{ + GNUNET_break (0); + return GNUNET_SYSERR; +} + + +int +TALER_MINT_DB_set_reveal_ok (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub) +{ + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "set_reveal_ok", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_update_known_coin (PGconn *db_conn, + const struct KnownCoin *known_coin) +{ + struct TALER_AmountNBO expended_nbo = TALER_amount_hton (known_coin->expended_balance); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.coin_pub), + TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.denom_pub), + TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.denom_sig), + TALER_DB_QUERY_PARAM_PTR(&expended_nbo.value), + TALER_DB_QUERY_PARAM_PTR(&expended_nbo.fraction), + TALER_DB_QUERY_PARAM_PTR_SIZED(expended_nbo.currency, strlen (expended_nbo.currency)), + TALER_DB_QUERY_PARAM_PTR(&known_coin->refresh_session_pub), + TALER_DB_QUERY_PARAM_END + }; + + if (GNUNET_NO == known_coin->is_refreshed) + { + // Mind the magic index! + params[6].data = NULL; + params[6].size = 0; + } + + PGresult *result = TALER_DB_exec_prepared (db_conn, "update_known_coin", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + PQclear (result); + // return 'no' here (don't fail) so that we can + // insert if update fails (=> "upsert") + return GNUNET_NO; + } + + PQclear (result); + return GNUNET_YES; +} + +int +TALER_MINT_DB_insert_known_coin (PGconn *db_conn, + const struct KnownCoin *known_coin) +{ + struct TALER_AmountNBO expended_nbo = TALER_amount_hton (known_coin->expended_balance); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.coin_pub), + TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.denom_pub), + TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.denom_sig), + TALER_DB_QUERY_PARAM_PTR(&expended_nbo.value), + TALER_DB_QUERY_PARAM_PTR(&expended_nbo.fraction), + TALER_DB_QUERY_PARAM_PTR_SIZED(&expended_nbo.currency, strlen (expended_nbo.currency)), + TALER_DB_QUERY_PARAM_PTR(&known_coin->refresh_session_pub), + TALER_DB_QUERY_PARAM_END + }; + + if (GNUNET_NO == known_coin->is_refreshed) + { + // Mind the magic index! + params[6].data = NULL; + params[6].size = 0; + } + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_known_coin", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + PQclear (result); + // return 'no' here (don't fail) so that we can + // update if insert fails (=> "upsert") + return GNUNET_NO; + } + + PQclear (result); + return GNUNET_YES; +} + + +int +TALER_MINT_DB_upsert_known_coin (PGconn *db_conn, struct KnownCoin *known_coin) +{ + int ret; + ret = TALER_MINT_DB_update_known_coin (db_conn, known_coin); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_YES == ret) + return GNUNET_YES; + return TALER_MINT_DB_insert_known_coin (db_conn, known_coin); +} + + +int +TALER_MINT_DB_insert_refresh_commit_link (PGconn *db_conn, struct RefreshCommitLink *commit_link) +{ + uint16_t cnc_index_nbo = htons (commit_link->cnc_index); + uint16_t oldcoin_index_nbo = htons (commit_link->oldcoin_index); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(&commit_link->session_pub), + TALER_DB_QUERY_PARAM_PTR(&commit_link->transfer_pub), + TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR_SIZED(&commit_link->shared_secret_enc, sizeof (struct GNUNET_HashCode)), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_commit_link", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_insert_refresh_commit_coin (PGconn *db_conn, struct RefreshCommitCoin *commit_coin) +{ + uint16_t cnc_index_nbo = htons (commit_coin->cnc_index); + uint16_t newcoin_index_nbo = htons (commit_coin->newcoin_index); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(&commit_coin->session_pub), + TALER_DB_QUERY_PARAM_PTR(&commit_coin->coin_ev), + TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR_SIZED(&commit_coin->link_enc, sizeof (struct LinkData)), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_commit_coin", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_get_refresh_commit_link (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + int cnc_index, int oldcoin_index, + struct RefreshCommitLink *cc) +{ + uint16_t cnc_index_nbo = htons (cnc_index); + uint16_t oldcoin_index_nbo = htons (oldcoin_index); + + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), + TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_DB_QUERY_PARAM_END + }; + + cc->cnc_index = cnc_index; + cc->oldcoin_index = oldcoin_index; + cc->session_pub = *refresh_session_pub; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_commit_link", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("transfer_pub", &cc->transfer_pub), + TALER_DB_RESULT_SPEC_SIZED("link_secret_enc", &cc->shared_secret_enc, + TALER_REFRESH_SHARED_SECRET_LENGTH), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_YES != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_free (cc); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_YES; +} + + +int +TALER_MINT_DB_get_refresh_commit_coin (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + int cnc_index, int newcoin_index, + struct RefreshCommitCoin *cc) +{ + uint16_t cnc_index_nbo = htons (cnc_index); + uint16_t newcoin_index_nbo = htons (newcoin_index); + + cc->cnc_index = cnc_index; + cc->newcoin_index = newcoin_index; + cc->session_pub = *refresh_session_pub; + + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), + TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_commit_coin", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("coin_ev", &cc->coin_ev), + TALER_DB_RESULT_SPEC_SIZED("link_vector_enc", &cc->link_enc, + TALER_REFRESH_LINK_LENGTH), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_YES != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_YES; +} + + +int +TALER_MINT_DB_get_refresh_order (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub) +{ + uint16_t newcoin_index_nbo = htons (newcoin_index); + + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_order", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + GNUNET_assert (1 == PQntuples (result)); + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("denom_pub", denom_pub), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_insert_refresh_collectable (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const struct TALER_RSA_Signature *ev_sig) +{ + uint16_t newcoin_index_nbo = htons (newcoin_index); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR(ev_sig), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_collectable", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_get_refresh_collectable (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + struct TALER_RSA_Signature *ev_sig) +{ + + uint16_t newcoin_index_nbo = htons (newcoin_index); + + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_collectable", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + GNUNET_assert (1 == PQntuples (result)); + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("ev_sig", ev_sig), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + + +int +TALER_MINT_DB_insert_refresh_melt (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t oldcoin_index, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub) +{ + uint16_t oldcoin_index_nbo = htons (oldcoin_index); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR(coin_pub), + TALER_DB_QUERY_PARAM_PTR(denom_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_melt", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + + +int +TALER_MINT_DB_get_refresh_melt (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t oldcoin_index, + struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub) +{ + uint16_t oldcoin_index_nbo = htons (oldcoin_index); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_melt", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + GNUNET_assert (1 == PQntuples (result)); + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("coin_pub", coin_pub), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_db_get_link (PGconn *db_conn, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + LinkIterator link_iter, + void *cls) +{ + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(coin_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_link", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + + int i = 0; + int res; + + for (i = 0; i < PQntuples (result); i++) + { + struct LinkDataEnc link_data_enc; + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + struct TALER_RSA_Signature ev_sig; + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("link_vector_enc", &link_data_enc), + TALER_DB_RESULT_SPEC("denom_pub", &denom_pub), + TALER_DB_RESULT_SPEC("ev_sig", &ev_sig), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, i)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != (res = link_iter (cls, &link_data_enc, &denom_pub, &ev_sig))) + { + GNUNET_assert (GNUNET_SYSERR != res); + PQclear (result); + return res; + } + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_db_get_transfer (PGconn *db_conn, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + struct GNUNET_CRYPTO_EcdsaPublicKey *transfer_pub, + struct SharedSecretEnc *shared_secret_enc) +{ + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(coin_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (db_conn, "get_transfer", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + if (1 != PQntuples (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "got %d tuples for get_transfer\n", PQntuples (result)); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("transfer_pub", transfer_pub), + TALER_DB_RESULT_SPEC("link_secret_enc", shared_secret_enc), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +int +TALER_MINT_DB_init_deposits (PGconn *conn, int tmp) +{ + const char *tmp_str = (1 == tmp) ? "TEMPORARY" : ""; + char *sql; + PGresult *res; + int ret; + + res = NULL; + (void) GNUNET_asprintf (&sql, + "CREATE %1$s TABLE IF NOT EXISTS deposits (" + " coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (length(coin_pub)=32)" + ",denom_pub BYTEA NOT NULL CHECK (length(denom_pub)=32)" + ",transaction_id INT8 NOT NULL" + ",amount_value INT4 NOT NULL" + ",amount_fraction INT4 NOT NULL" + ",amount_currency VARCHAR(4) NOT NULL" + ",merchant_pub BYTEA NOT NULL" + ",h_contract BYTEA NOT NULL CHECK (length(h_contract)=64)" + ",h_wire BYTEA NOT NULL CHECK (length(h_wire)=64)" + ",coin_sig BYTEA NOT NULL CHECK (length(coin_sig)=64)" + ",wire TEXT NOT NULL" + ")", + tmp_str); + res = PQexec (conn, sql); + GNUNET_free (sql); + if (PGRES_COMMAND_OK != PQresultStatus (res)) + { + break_db_err (res); + ret = GNUNET_SYSERR; + } + else + ret = GNUNET_OK; + PQclear (res); + return ret; +} + +int +TALER_MINT_DB_prepare_deposits (PGconn *db_conn) +{ + PGresult *result; + + result = PQprepare (db_conn, "insert_deposit", + "INSERT INTO deposits (" + "coin_pub," + "denom_pub," + "transaction_id," + "amount_value," + "amount_fraction," + "amount_currency," + "merchant_pub," + "h_contract," + "h_wire," + "coin_sig," + "wire" + ") VALUES (" + "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11" + ")", + 11, NULL); + EXITIF (PGRES_COMMAND_OK != PQresultStatus(result)); + PQclear (result); + + result = PQprepare (db_conn, "get_deposit", + "SELECT " + "coin_pub," + "denom_pub," + "transaction_id," + "amount_value," + "amount_fraction," + "amount_currency," + "merchant_pub," + "h_contract," + "h_wire," + "coin_sig" + " FROM deposits WHERE (" + "coin_pub = $1" + ")", + 1, NULL); + EXITIF (PGRES_COMMAND_OK != PQresultStatus(result)); + PQclear (result); + + return GNUNET_OK; + + EXITIF_exit: + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; +} + + +int +TALER_MINT_DB_insert_deposit (PGconn *db_conn, + const struct Deposit *deposit) +{ + struct TALER_DB_QueryParam params[]= { + TALER_DB_QUERY_PARAM_PTR (&deposit->coin_pub), + TALER_DB_QUERY_PARAM_PTR (&deposit->denom_pub), + TALER_DB_QUERY_PARAM_PTR (&deposit->transaction_id), + TALER_DB_QUERY_PARAM_PTR (&deposit->amount.value), + TALER_DB_QUERY_PARAM_PTR (&deposit->amount.fraction), + TALER_DB_QUERY_PARAM_PTR_SIZED (deposit->amount.currency, strlen (deposit->amount.currency)), + TALER_DB_QUERY_PARAM_PTR (&deposit->merchant_pub), + TALER_DB_QUERY_PARAM_PTR (&deposit->h_contract), + TALER_DB_QUERY_PARAM_PTR (&deposit->h_wire), + TALER_DB_QUERY_PARAM_PTR (&deposit->coin_sig), + TALER_DB_QUERY_PARAM_PTR_SIZED (deposit->wire, strlen(deposit->wire)), + TALER_DB_QUERY_PARAM_END + }; + PGresult *result; + + result = TALER_DB_exec_prepared (db_conn, "insert_deposit", params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + break_db_err (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + +int +TALER_MINT_DB_get_deposit (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *coin_pub, + struct Deposit **r_deposit) +{ + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (coin_pub), + TALER_DB_QUERY_PARAM_END + }; + PGresult *result; + struct Deposit *deposit; + + deposit = NULL; + result = TALER_DB_exec_prepared (db_conn, "get_deposit", params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + break_db_err (result); + goto EXITIF_exit; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + if (1 != PQntuples (result)) + { + GNUNET_break (0); + goto EXITIF_exit; + } + + { + deposit = GNUNET_malloc (sizeof (struct Deposit)); /* Without wire data */ + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC ("coin_pub", &deposit->coin_pub), + TALER_DB_RESULT_SPEC ("denom_pub", &deposit->denom_pub), + TALER_DB_RESULT_SPEC ("coin_sig", &deposit->coin_sig), + TALER_DB_RESULT_SPEC ("transaction_id", &deposit->transaction_id), + TALER_DB_RESULT_SPEC ("merchant_pub", &deposit->merchant_pub), + TALER_DB_RESULT_SPEC ("h_contract", &deposit->h_contract), + TALER_DB_RESULT_SPEC ("h_wire", &deposit->h_wire), + TALER_DB_RESULT_SPEC_END + }; + EXITIF (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)); + EXITIF (GNUNET_OK != TALER_DB_extract_amount_nbo (result, 0, + "amount_value", + "amount_fraction", + "amount_currency", + &deposit->amount)); + deposit->purpose.purpose = htonl (TALER_SIGNATURE_DEPOSIT); + deposit->purpose.size = htonl (sizeof (struct Deposit) + - offsetof (struct Deposit, purpose)); + } + + PQclear (result); + *r_deposit = deposit; + return GNUNET_OK; + +EXITIF_exit: + PQclear (result); + GNUNET_free_non_null (deposit); + deposit = NULL; + return GNUNET_SYSERR; +} + + + +/** + * Get the thread-local database-handle. + * Connect to the db if the connection does not exist yet. + * + * @param the database connection, or NULL on error + */ +PGconn * +TALER_MINT_DB_get_connection (void) +{ + PGconn *db_conn; + + if (NULL != (db_conn = pthread_getspecific (db_conn_threadlocal))) + return db_conn; + + db_conn = PQconnectdb (TALER_MINT_db_connection_cfg_str); + + if (CONNECTION_OK != PQstatus (db_conn)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "db connection failed: %s\n", + PQerrorMessage (db_conn)); + GNUNET_break (0); + return NULL; + } + + if (GNUNET_OK != TALER_MINT_DB_prepare (db_conn)) + { + GNUNET_break (0); + return NULL; + } + if (0 != pthread_setspecific (db_conn_threadlocal, db_conn)) + { + GNUNET_break (0); + return NULL; + } + return db_conn; +} + + +/** + * Close thread-local database connection when a thread is destroyed. + * + * @param closure we get from pthreads (the db handle) + */ +static void +db_conn_destroy (void *cls) +{ + PGconn *db_conn = cls; + if (NULL != db_conn) + PQfinish (db_conn); +} + + +/** + * Initialize database subsystem. + * + * @param connection_cfg configuration to use to talk to DB + * @return GNUNET_OK on success + */ +int +TALER_MINT_DB_init (const char *connection_cfg) +{ + + if (0 != pthread_key_create (&db_conn_threadlocal, &db_conn_destroy)) + { + fprintf (stderr, + "Can't create pthread key.\n"); + return GNUNET_SYSERR; + } + TALER_MINT_db_connection_cfg_str = GNUNET_strdup (connection_cfg); + return GNUNET_OK; +} diff --git a/src/mint/mint_db.h b/src/mint/mint_db.h new file mode 100644 index 000000000..4f47aac1c --- /dev/null +++ b/src/mint/mint_db.h @@ -0,0 +1,344 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint/mint_db.h + * @brief Mint-specific database access + * @author Florian Dold + */ + +#ifndef _NEURO_MINT_DB_H +#define _NEURO_MINT_DB_H + +#include <libpq-fe.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#include "taler_types.h" +#include "taler_rsa.h" + + +/** + * Reserve row. Corresponds to table 'reserves' in + * the mint's database. + */ +struct Reserve +{ + /** + * Signature over the purse. + * Only valid if (blind_session_missing==GNUNET_YES). + */ + struct GNUNET_CRYPTO_EddsaSignature status_sig; + /** + * Signature with purpose TALER_SIGNATURE_PURSE. + * Only valid if (blind_session_missing==GNUNET_YES). + */ + struct GNUNET_CRYPTO_EccSignaturePurpose status_sig_purpose; + /** + * Signing key used to sign the purse. + * Only valid if (blind_session_missing==GNUNET_YES). + */ + struct GNUNET_CRYPTO_EddsaPublicKey status_sign_pub; + /** + * Withdraw public key, identifies the purse. + * Only the customer knows the corresponding private key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; + /** + * Remaining balance in the purse. + */ + struct TALER_AmountNBO balance; + + /** + * Expiration date for the purse. + */ + struct GNUNET_TIME_AbsoluteNBO expiration; +}; + + +struct CollectableBlindcoin +{ + struct TALER_RSA_BlindedSignaturePurpose ev; + struct TALER_RSA_Signature ev_sig; + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; + struct GNUNET_CRYPTO_EddsaSignature reserve_sig; +}; + + +struct RefreshSession +{ + int has_commit_sig; + struct GNUNET_CRYPTO_EddsaSignature commit_sig; + struct GNUNET_CRYPTO_EddsaPublicKey session_pub; + uint16_t num_oldcoins; + uint16_t num_newcoins; + uint16_t kappa; + uint16_t noreveal_index; + uint8_t reveal_ok; +}; + + +#define TALER_REFRESH_SHARED_SECRET_LENGTH (sizeof (struct GNUNET_HashCode)) +#define TALER_REFRESH_LINK_LENGTH (sizeof (struct LinkData)) + +struct RefreshCommitLink +{ + struct GNUNET_CRYPTO_EddsaPublicKey session_pub; + struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub; + uint16_t cnc_index; + uint16_t oldcoin_index; + char shared_secret_enc[sizeof (struct GNUNET_HashCode)]; +}; + +struct LinkData +{ + struct GNUNET_CRYPTO_EcdsaPrivateKey coin_priv; + struct TALER_RSA_BlindingKeyBinaryEncoded bkey_enc; +}; + + +GNUNET_NETWORK_STRUCT_BEGIN + +struct SharedSecretEnc +{ + char data[TALER_REFRESH_SHARED_SECRET_LENGTH]; +}; + + +struct LinkDataEnc +{ + char data[sizeof (struct LinkData)]; +}; + +GNUNET_NETWORK_STRUCT_END + +struct RefreshCommitCoin +{ + struct GNUNET_CRYPTO_EddsaPublicKey session_pub; + struct TALER_RSA_BlindedSignaturePurpose coin_ev; + uint16_t cnc_index; + uint16_t newcoin_index; + char link_enc[sizeof (struct LinkData)]; +}; + + +struct KnownCoin +{ + struct TALER_CoinPublicInfo public_info; + struct TALER_Amount expended_balance; + int is_refreshed; + /** + * Refreshing session, only valid if + * is_refreshed==1. + */ + struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub; +}; + +GNUNET_NETWORK_STRUCT_BEGIN + +struct Deposit +{ + /* FIXME: should be TALER_CoinPublicInfo */ + struct GNUNET_CRYPTO_EddsaPublicKey coin_pub; + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + struct TALER_RSA_Signature coin_sig; + struct TALER_RSA_SignaturePurpose purpose; + uint64_t transaction_id; + struct TALER_AmountNBO amount; + struct GNUNET_CRYPTO_EddsaPublicKey merchant_pub; + struct GNUNET_HashCode h_contract; + struct GNUNET_HashCode h_wire; + /* TODO: uint16_t wire_size */ + char wire[]; /* string encoded wire JSON object */ +}; + +GNUNET_NETWORK_STRUCT_END + +int +TALER_MINT_DB_prepare (PGconn *db_conn); + +int +TALER_MINT_DB_get_collectable_blindcoin (PGconn *db_conn, + struct TALER_RSA_BlindedSignaturePurpose *blind_ev, + struct CollectableBlindcoin *collectable); + +int +TALER_MINT_DB_insert_collectable_blindcoin (PGconn *db_conn, + const struct CollectableBlindcoin *collectable); + + +int +TALER_MINT_DB_rollback (PGconn *db_conn); + + +int +TALER_MINT_DB_transaction (PGconn *db_conn); + + +int +TALER_MINT_DB_commit (PGconn *db_conn); + + +int +TALER_MINT_DB_get_reserve (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub, + struct Reserve *reserve_res); + +int +TALER_MINT_DB_update_reserve (PGconn *db_conn, + const struct Reserve *reserve, + int fresh); + + +int +TALER_MINT_DB_insert_refresh_order (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub); + +int +TALER_MINT_DB_get_refresh_session (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + struct RefreshSession *r_session); + + +int +TALER_MINT_DB_get_known_coin (PGconn *db_conn, struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + struct KnownCoin *known_coin); + + +int +TALER_MINT_DB_upsert_known_coin (PGconn *db_conn, struct KnownCoin *known_coin); + + +int +TALER_MINT_DB_insert_refresh_commit_link (PGconn *db_conn, struct RefreshCommitLink *commit_link); + +int +TALER_MINT_DB_insert_refresh_commit_coin (PGconn *db_conn, struct RefreshCommitCoin *commit_coin); + + +int +TALER_MINT_DB_get_refresh_commit_link (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + int i, int j, + struct RefreshCommitLink *commit_link); + + +int +TALER_MINT_DB_get_refresh_commit_coin (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + int i, int j, + struct RefreshCommitCoin *commit_coin); + + +int +TALER_MINT_DB_create_refresh_session (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey + *session_pub); + + +int +TALER_MINT_DB_get_refresh_order (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub); + + +int +TALER_MINT_DB_insert_refresh_collectable (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const struct TALER_RSA_Signature *ev_sig); +int +TALER_MINT_DB_get_refresh_collectable (PGconn *db_conn, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + struct TALER_RSA_Signature *ev_sig); +int +TALER_MINT_DB_set_reveal_ok (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub); + +int +TALER_MINT_DB_insert_refresh_melt (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t oldcoin_index, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub); + + +int +TALER_MINT_DB_get_refresh_melt (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t oldcoin_index, + struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub); + + +typedef +int (*LinkIterator) (void *cls, + const struct LinkDataEnc *link_data_enc, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub, + const struct TALER_RSA_Signature *ev_sig); + +int +TALER_db_get_link (PGconn *db_conn, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + LinkIterator link_iter, + void *cls); + + +int +TALER_db_get_transfer (PGconn *db_conn, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + struct GNUNET_CRYPTO_EcdsaPublicKey *transfer_pub, + struct SharedSecretEnc *shared_secret_enc); + +int +TALER_MINT_DB_init_deposits (PGconn *db_conn, int temporary); + +int +TALER_MINT_DB_prepare_deposits (PGconn *db_conn); + +int +TALER_MINT_DB_insert_deposit (PGconn *db_conn, + const struct Deposit *deposit); + +int +TALER_MINT_DB_get_deposit (PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *coin_pub, + struct Deposit **r_deposit); +int +TALER_MINT_DB_insert_known_coin (PGconn *db_conn, + const struct KnownCoin *known_coin); + + + +/** + * Get the thread-local database-handle. + * Connect to the db if the connection does not exist yet. + * + * @param the database connection, or NULL on error + */ +PGconn * +TALER_MINT_DB_get_connection (void); + + +int +TALER_MINT_DB_init (const char *connection_cfg); + + + +#endif /* _NEURO_MINT_DB_H */ diff --git a/src/mint/taler-mint-dbinit.c b/src/mint/taler-mint-dbinit.c new file mode 100644 index 000000000..d877f62c6 --- /dev/null +++ b/src/mint/taler-mint-dbinit.c @@ -0,0 +1,285 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-dbinit.c + * @brief Create tables for the mint database. + * @author Florian Dold + */ + +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <libpq-fe.h> +#include "mint.h" + + +#define break_db_err(result) do { \ + GNUNET_break(0); \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \ + PQclear (result); \ + } while (0) + + +static char *mint_base_dir; +static struct GNUNET_CONFIGURATION_Handle *cfg; +static PGconn *db_conn; +static char *TALER_MINT_db_connection_cfg_str; + + +int +TALER_MINT_init_withdraw_tables (PGconn *conn) +{ + PGresult *result; + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS reserves" + "(" + " reserve_pub BYTEA PRIMARY KEY" + ",balance_value INT4 NOT NULL" + ",balance_fraction INT4 NOT NULL" + ",balance_currency VARCHAR(4) NOT NULL" + ",status_sig BYTEA" + ",status_sign_pub BYTEA" + ",expiration_date INT8 NOT NULL" + ")"); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS collectable_blindcoins" + "(" + "blind_ev BYTEA PRIMARY KEY" + ",blind_ev_sig BYTEA NOT NULL" + ",denom_pub BYTEA NOT NULL" + ",reserve_sig BYTEA NOT NULL" + ",reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub)" + ")"); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS known_coins " + "(" + " coin_pub BYTEA NOT NULL PRIMARY KEY" + ",denom_pub BYTEA NOT NULL" + ",denom_sig BYTEA NOT NULL" + ",expended_value INT4 NOT NULL" + ",expended_fraction INT4 NOT NULL" + ",expended_currency VARCHAR(4) NOT NULL" + ",refresh_session_pub BYTEA" + ")"); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS refresh_sessions " + "(" + " session_pub BYTEA PRIMARY KEY CHECK (length(session_pub) = 32)" + ",session_melt_sig BYTEA" + ",session_commit_sig BYTEA" + ",noreveal_index INT2 NOT NULL" + // non-zero if all reveals were ok + // and the new coin signatures are ready + ",reveal_ok BOOLEAN NOT NULL DEFAULT false" + ") "); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS refresh_order " + "( " + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub)" + ",newcoin_index INT2 NOT NULL " + ",denom_pub BYTEA NOT NULL " + ",PRIMARY KEY (session_pub, newcoin_index)" + ") "); + + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS refresh_commit_link" + "(" + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub)" + ",transfer_pub BYTEA NOT NULL" + ",link_secret_enc BYTEA NOT NULL" + // index of the old coin in the customer's request + ",oldcoin_index INT2 NOT NULL" + // index for cut and choose, + // ranges from 0 to kappa-1 + ",cnc_index INT2 NOT NULL" + ")"); + + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS refresh_commit_coin" + "(" + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) " + ",link_vector_enc BYTEA NOT NULL" + // index of the new coin in the customer's request + ",newcoin_index INT2 NOT NULL" + // index for cut and choose, + ",cnc_index INT2 NOT NULL" + ",coin_ev BYTEA NOT NULL" + ")"); + + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS refresh_melt" + "(" + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) " + ",coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) " + ",denom_pub BYTEA NOT NULL " + ",oldcoin_index INT2 NOT NULL" + ")"); + + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS refresh_collectable" + "(" + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) " + ",ev_sig BYTEA NOT NULL" + ",newcoin_index INT2 NOT NULL" + ")"); + + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + result = PQexec (conn, + "CREATE TABLE IF NOT EXISTS deposits " + "( " + " coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (length(coin_pub)=32)" + ",denom_pub BYTEA NOT NULL CHECK (length(denom_pub)=32)" + ",transaction_id INT8 NOT NULL" + ",amount_currency VARCHAR(4) NOT NULL" + ",amount_value INT4 NOT NULL" + ",amount_fraction INT4 NOT NULL" + ",merchant_pub BYTEA NOT NULL" + ",h_contract BYTEA NOT NULL CHECK (length(h_contract)=64)" + ",h_wire BYTEA NOT NULL CHECK (length(h_wire)=64)" + ",coin_sig BYTEA NOT NULL CHECK (length(coin_sig)=64)" + ",wire TEXT NOT NULL" + ")"); + + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + break_db_err (result); + return GNUNET_SYSERR; + } + PQclear (result); + + return GNUNET_OK; +} + + +/** + * The main function of the serve tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"), + {'d', "mint-dir", "DIR", + "mint directory", 1, + &GNUNET_GETOPT_set_filename, &mint_base_dir}, + GNUNET_GETOPT_OPTION_END + }; + + if (GNUNET_GETOPT_run ("taler-mint-serve", options, argc, argv) < 0) + return 1; + + GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("taler-mint-dbinit", "INFO", NULL)); + + if (NULL == mint_base_dir) + { + fprintf (stderr, "Mint base directory not given.\n"); + return 1; + } + + cfg = TALER_MINT_config_load (mint_base_dir); + if (NULL == cfg) + { + fprintf (stderr, "Can't load mint configuration.\n"); + return 1; + } + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "mint", "db", &TALER_MINT_db_connection_cfg_str)) + { + fprintf (stderr, "Configuration 'mint.db' not found.\n"); + return 42; + } + db_conn = PQconnectdb (TALER_MINT_db_connection_cfg_str); + if (CONNECTION_OK != PQstatus (db_conn)) + { + fprintf (stderr, "Database connection failed: %s\n", PQerrorMessage (db_conn)); + return 1; + } + + if (GNUNET_OK != TALER_MINT_init_withdraw_tables (db_conn)) + { + fprintf (stderr, "Failed to initialize database.\n"); + return 1; + } + + return 0; +} + diff --git a/src/mint/taler-mint-httpd.c b/src/mint/taler-mint-httpd.c new file mode 100644 index 000000000..6d69813c0 --- /dev/null +++ b/src/mint/taler-mint-httpd.c @@ -0,0 +1,376 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd.c + * @brief Serve the HTTP interface of the mint + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <libpq-fe.h> +#include <pthread.h> +#include "mint.h" +#include "mint_db.h" +#include "taler_types.h" +#include "taler_signatures.h" +#include "taler_rsa.h" +#include "taler_json_lib.h" +#include "taler_microhttpd_lib.h" +#include "taler-mint-httpd_mhd.h" +#include "taler-mint-httpd_keys.h" +#include "taler-mint-httpd_deposit.h" +#include "taler-mint-httpd_withdraw.h" +#include "taler-mint-httpd_refresh.h" + + +/** + * Base directory of the mint (global) + */ +char *mintdir; + +/** + * The mint's configuration (global) + */ +struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Master public key (according to the + * configuration in the mint directory). + */ +struct GNUNET_CRYPTO_EddsaPublicKey master_pub; + +/** + * The HTTP Daemon. + */ +static struct MHD_Daemon *mydaemon; + +/** + * The kappa value for refreshing. + */ +static unsigned int refresh_security_parameter; + +/** + * Port to run the daemon on. + */ +static uint16_t serve_port; + + +/** + * Convert a string representing an EdDSA signature to an EdDSA + * signature. + * + * FIXME: this should be in GNUnet. + * FIXME: why? this code is dead, even here! + * + * @param enc encoded EdDSA signature + * @param enclen number of bytes in @a enc (without 0-terminator) + * @param pub where to store the EdDSA signature + * @return #GNUNET_OK on success + */ +int +TALER_eddsa_signature_from_string (const char *enc, + size_t enclen, + struct GNUNET_CRYPTO_EddsaSignature *sig) +{ + size_t keylen = (sizeof (struct GNUNET_CRYPTO_EddsaSignature)) * 8; + + if (keylen % 5 > 0) + keylen += 5 - keylen % 5; + keylen /= 5; + if (enclen != keylen) + return GNUNET_SYSERR; + + if (GNUNET_OK != GNUNET_STRINGS_string_to_data (enc, enclen, + sig, + sizeof (struct GNUNET_CRYPTO_EddsaSignature))) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +/** + * Handle a request coming from libmicrohttpd. + * + * @param cls closure for MHD daemon (unused) + * @param connection the connection + * @param url the requested url + * @param method the method (POST, GET, ...) + * @param upload_data request data + * @param upload_data_size size of @a upload_data in bytes + * @param con_cls closure for request (a `struct Buffer *`) + * @return MHD result code + */ +static int +handle_mhd_request (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **con_cls) +{ + static struct RequestHandler handlers[] = + { + { "/", MHD_HTTP_METHOD_GET, "text/plain", + "Hello, I'm the mint\n", 0, + &TALER_MINT_handler_static_response, MHD_HTTP_OK }, + { "/agpl", MHD_HTTP_METHOD_GET, "text/plain", + NULL, 0, + &TALER_MINT_handler_agpl_redirect, MHD_HTTP_FOUND }, + { "/keys", MHD_HTTP_METHOD_GET, "application/json", + NULL, 0, + &TALER_MINT_handler_keys, MHD_HTTP_OK }, + { "/keys", NULL, "text/plain", + "Only GET is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/withdraw/status", MHD_HTTP_METHOD_GET, "application/json", + NULL, 0, + &TALER_MINT_handler_withdraw_status, MHD_HTTP_OK }, + { "/withdraw/status", NULL, "text/plain", + "Only GET is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/withdraw/sign", MHD_HTTP_METHOD_GET, "application/json", + NULL, 0, + &TALER_MINT_handler_withdraw_sign, MHD_HTTP_OK }, + { "/withdraw/sign", NULL, "text/plain", + "Only GET is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/refresh/melt", MHD_HTTP_METHOD_POST, "application/json", + NULL, 0, + &TALER_MINT_handler_refresh_melt, MHD_HTTP_OK }, + { "/refresh/melt", NULL, "text/plain", + "Only POST is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/refresh/commit", MHD_HTTP_METHOD_POST, "application/json", + NULL, 0, + &TALER_MINT_handler_refresh_commit, MHD_HTTP_OK }, + { "/refresh/commit", NULL, "text/plain", + "Only POST is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/refresh/reveal", MHD_HTTP_METHOD_POST, "application/json", + NULL, 0, + &TALER_MINT_handler_refresh_melt, MHD_HTTP_OK }, + { "/refresh/reveal", NULL, "text/plain", + "Only POST is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/refresh/link", MHD_HTTP_METHOD_GET, "application/json", + NULL, 0, + &TALER_MINT_handler_refresh_link, MHD_HTTP_OK }, + { "/refresh/link", NULL, "text/plain", + "Only GET is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/refresh/reveal", MHD_HTTP_METHOD_GET, "application/json", + NULL, 0, + &TALER_MINT_handler_refresh_reveal, MHD_HTTP_OK }, + { "/refresh/reveal", NULL, "text/plain", + "Only GET is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { "/deposit", MHD_HTTP_METHOD_POST, "application/json", + NULL, 0, + &TALER_MINT_handler_deposit, MHD_HTTP_OK }, + { "/deposit", NULL, "text/plain", + "Only POST is allowed", 0, + &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, + { NULL, NULL, NULL, NULL, 0, 0 } + }; + static struct RequestHandler h404 = + { + "", NULL, "text/html", + "<html><title>404: not found</title></html>", 0, + &TALER_MINT_handler_static_response, MHD_HTTP_NOT_FOUND + }; + struct RequestHandler *rh; + unsigned int i; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling request for URL '%s'\n", + url); + for (i=0;NULL != handlers[i].url;i++) + { + rh = &handlers[i]; + if ( (0 == strcasecmp (url, + rh->url)) && + ( (NULL == rh->method) || + (0 == strcasecmp (method, + rh->method)) ) ) + return rh->handler (rh, + connection, + con_cls, + upload_data, + upload_data_size); + } + return TALER_MINT_handler_static_response (&h404, + connection, + con_cls, + upload_data, + upload_data_size); +} + + + +/** + * Load configuration parameters for the mint + * server into the corresponding global variables. + * + * @param param mint_directory the mint's directory + * @return GNUNET_OK on success + */ +static int +mint_serve_process_config (const char *mint_directory) +{ + unsigned long long port; + unsigned long long kappa; + char *master_pub_str; + char *db_cfg; + + cfg = TALER_MINT_config_load (mint_directory); + if (NULL == cfg) + { + fprintf (stderr, + "can't load mint configuration\n"); + return 1; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "mint", "master_pub", + &master_pub_str)) + { + fprintf (stderr, + "No master public key given in mint configuration."); + return GNUNET_NO; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_public_key_from_string (master_pub_str, + strlen (master_pub_str), + &master_pub)) + { + fprintf (stderr, + "Invalid master public key given in mint configuration."); + return GNUNET_NO; + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "mint", "db", + &db_cfg)) + { + fprintf (stderr, + "invalid configuration: mint.db\n"); + return GNUNET_NO; + } + if (GNUNET_OK != + TALER_MINT_DB_init (db_cfg)) + { + fprintf (stderr, + "failed to initialize DB subsystem\n"); + return GNUNET_NO; + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + "mint", "port", + &port)) + { + fprintf (stderr, + "invalid configuration: mint.port\n"); + return GNUNET_NO; + } + + if ((port == 0) || (port > UINT16_MAX)) + { + fprintf (stderr, + "invalid configuration (value out of range): mint.port\n"); + return GNUNET_NO; + } + serve_port = port; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + "mint", "refresh_security_parameter", + &kappa)) + { + fprintf (stderr, + "invalid configuration: mint.refresh_security_parameter\n"); + return GNUNET_NO; + } + refresh_security_parameter = kappa; + + return GNUNET_OK; +} + + +/** + * The main function of the serve tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"), + {'d', "mint-dir", "DIR", + "mint directory", 1, + &GNUNET_GETOPT_set_filename, &mintdir}, + GNUNET_GETOPT_OPTION_END + }; + int ret; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-mint-serve", + "INFO", + NULL)); + if (GNUNET_GETOPT_run ("taler-mint-serve", + options, + argc, argv) < 0) + return 1; + if (NULL == mintdir) + { + fprintf (stderr, + "no mint dir given\n"); + return 1; + } + + if (GNUNET_OK != mint_serve_process_config (mintdir)) + return 1; + + + mydaemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, + serve_port, + NULL, NULL, + &handle_mhd_request, NULL, + MHD_OPTION_END); + + if (NULL == mydaemon) + { + fprintf (stderr, + "Failed to start MHD.\n"); + return 1; + } + + ret = TALER_MINT_key_reload_loop (); + MHD_stop_daemon (mydaemon); + return (GNUNET_OK == ret) ? 0 : 1; +} + diff --git a/src/mint/taler-mint-httpd.h b/src/mint/taler-mint-httpd.h new file mode 100644 index 000000000..59f38aadb --- /dev/null +++ b/src/mint/taler-mint-httpd.h @@ -0,0 +1,106 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd.h + * @brief Global declarations for the mint + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_H +#define TALER_MINT_HTTPD_H + + +/** + * Cut-and-choose size for refreshing. + * FIXME: maybe make it a config option? + */ +#define KAPPA 3 + + +/** + * The mint's configuration. + */ +extern struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Main directory with mint data. + */ +extern char *mintdir; + +/** + * Master public key (according to the + * configuration in the mint directory). + */ +extern struct GNUNET_CRYPTO_EddsaPublicKey master_pub; + + +/** + * Struct describing an URL and the handler for it. + */ +struct RequestHandler +{ + + /** + * URL the handler is for. + */ + const char *url; + + /** + * Method the handler is for, NULL for "all". + */ + const char *method; + + /** + * Mime type to use in reply (hint, can be NULL). + */ + const char *mime_type; + + /** + * Raw data for the @e handler + */ + const void *data; + + /** + * Number of bytes in @e data, 0 for 0-terminated. + */ + size_t data_size; + + /** + * Function to call to handle the request. + * + * @param rh this struct + * @param mime_type the @e mime_type for the reply (hint, can be NULL) + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ + int (*handler)(struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + /** + * Default response code. + */ + int response_code; +}; + + +#endif diff --git a/src/mint/taler-mint-httpd_deposit.c b/src/mint/taler-mint-httpd_deposit.c new file mode 100644 index 000000000..ecbc5c13b --- /dev/null +++ b/src/mint/taler-mint-httpd_deposit.c @@ -0,0 +1,270 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_deposit.c + * @brief Handle /deposit requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <libpq-fe.h> +#include <pthread.h> +#include "mint.h" +#include "mint_db.h" +#include "taler_types.h" +#include "taler_signatures.h" +#include "taler_rsa.h" +#include "taler_json_lib.h" +#include "taler_microhttpd_lib.h" +#include "taler-mint-httpd_keys.h" +#include "taler-mint-httpd_deposit.h" + + +/** + * Send confirmation of deposit success to client. + * + * @param connection connection to the client + * @param deposit deposit request to confirm + * @return MHD result code + */ +static int +helper_deposit_send_response_success (struct MHD_Connection *connection, + struct Deposit *deposit) +{ + // FIXME: return more information here + return request_send_json_pack (connection, MHD_HTTP_OK, + "{s:s}", "status", "DEPOSIT_OK"); +} + + +/** + * Handle a "/deposit" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_deposit (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + json_t *json; + struct Deposit *deposit; + json_t *wire; + json_t *resp; + char *wire_enc = NULL; + const char *deposit_type; + struct MintKeyState *key_state; + struct TALER_CoinPublicInfo coin_info; + struct TALER_RSA_Signature ubsig; + size_t len; + int resp_code; + PGconn *db_conn; + int res; + + res = process_post_json (connection, + connection_cls, + upload_data, upload_data_size, + &json); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + return MHD_YES; + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + deposit = NULL; + wire = NULL; + resp = NULL; + if (-1 == json_unpack (json, + "{s:s s:o}", + "type", &deposit_type, + "wire", &wire)) + { + GNUNET_break_op (0); + resp = json_pack ("{s:s}", "error", "Bad format"); + resp_code = MHD_HTTP_BAD_REQUEST; + goto EXITIF_exit; + } + if (NULL == (wire_enc = json_dumps (wire, JSON_COMPACT|JSON_SORT_KEYS))) + { + GNUNET_break_op (0); + resp = json_pack ("{s:s}", "error", "Bad format"); + resp_code = MHD_HTTP_BAD_REQUEST; + goto EXITIF_exit; + } + len = strlen (wire_enc) + 1; + deposit = GNUNET_malloc (sizeof (struct Deposit) + len); +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) +#define PARSE_DATA(field, addr) \ + EXITIF (GNUNET_OK != request_json_require_nav \ + (connection, json, \ + JNAV_FIELD, field, JNAV_RET_DATA, addr, sizeof (*addr))) + PARSE_DATA ("coin_pub", &deposit->coin_pub); + PARSE_DATA ("denom_pub", &deposit->denom_pub); + PARSE_DATA ("ubsig", &ubsig); + PARSE_DATA ("merchant_pub", &deposit->merchant_pub); + PARSE_DATA ("H_a", &deposit->h_contract); + PARSE_DATA ("H_wire", &deposit->h_wire); + PARSE_DATA ("csig", &deposit->coin_sig); + PARSE_DATA ("transaction_id", &deposit->transaction_id); +#undef PARSE_DATA + if (0 == strcmp ("DIRECT_DEPOSIT", deposit_type)) + deposit->purpose.purpose = htonl (TALER_SIGNATURE_DEPOSIT); + else if (0 == strcmp ("INCREMENTAL_DEPOSIT", deposit_type)) + deposit->purpose.purpose = htonl (TALER_SIGNATURE_INCREMENTAL_DEPOSIT); + else + { + GNUNET_break_op (0); + resp = json_pack ("{s:s}", "error", "Bad format"); + resp_code = MHD_HTTP_BAD_REQUEST; + goto EXITIF_exit; + } + deposit->purpose.size = htonl (sizeof (struct Deposit) + - offsetof (struct Deposit, purpose)); + memcpy (&coin_info.coin_pub, + &deposit->coin_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); + coin_info.denom_pub = deposit->denom_pub; + coin_info.denom_sig = ubsig; + key_state = TALER_MINT_key_state_acquire (); + if (GNUNET_YES != TALER_MINT_test_coin_valid (key_state, + &coin_info)) + { + TALER_MINT_key_state_release (key_state); + resp = json_pack ("{s:s}", "error", "Coin is not valid"); + resp_code = MHD_HTTP_NOT_FOUND; + goto EXITIF_exit; + } + TALER_MINT_key_state_release (key_state); + /* + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_DEPOSIT, + &deposit->purpose, + &deposit->coin_sig, + &deposit->coin_pub)) + { + resp = json_pack ("{s:s}", "error", "Signature verfication failed"); + resp_code = MHD_HTTP_NOT_FOUND; + goto EXITIF_exit; + } + */ + + /* Check if we already received the same deposit permission, + * or the coin was already deposited */ + + { + struct Deposit *existing_deposit; + int res; + + res = TALER_MINT_DB_get_deposit (db_conn, + &deposit->coin_pub, + &existing_deposit); + if (GNUNET_YES == res) + { + // FIXME: memory leak + if (0 == memcmp (existing_deposit, deposit, sizeof (struct Deposit))) + return helper_deposit_send_response_success (connection, deposit); + // FIXME: in the future, check if there's enough credits + // left on the coin. For now: refuse + // FIXME: return more information here + return request_send_json_pack (connection, MHD_HTTP_FORBIDDEN, + "{s:s}", + "error", "double spending"); + } + + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + + { + struct KnownCoin known_coin; + int res; + + res = TALER_MINT_DB_get_known_coin (db_conn, &coin_info.coin_pub, &known_coin); + if (GNUNET_YES == res) + { + // coin must have been refreshed + // FIXME: check + // FIXME: return more information here + return request_send_json_pack (connection, MHD_HTTP_FORBIDDEN, + "{s:s}", + "error", "coin was refreshed"); + } + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + /* coin valid but not known => insert into DB */ + known_coin.is_refreshed = GNUNET_NO; + known_coin.expended_balance = TALER_amount_ntoh (deposit->amount); + known_coin.public_info = coin_info; + + if (GNUNET_OK != TALER_MINT_DB_insert_known_coin (db_conn, &known_coin)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + + if (GNUNET_OK != TALER_MINT_DB_insert_deposit (db_conn, deposit)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return helper_deposit_send_response_success (connection, deposit); + + EXITIF_exit: + if (NULL != resp) + res = send_response_json (connection, resp, resp_code); + else + res = MHD_NO; + if (NULL != wire) + json_decref (wire); + if (NULL != deposit) + GNUNET_free (deposit); + if (NULL != wire_enc) + GNUNET_free (wire_enc); + return res; +#undef EXITIF +#undef PARSE_DATA +} + +/* end of taler-mint-httpd_deposit.c */ diff --git a/src/mint/taler-mint-httpd_deposit.h b/src/mint/taler-mint-httpd_deposit.h new file mode 100644 index 000000000..dd7b8c133 --- /dev/null +++ b/src/mint/taler-mint-httpd_deposit.h @@ -0,0 +1,48 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_deposit.h + * @brief Handle /deposit requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_DEPOSIT_H +#define TALER_MINT_HTTPD_DEPOSIT_H + +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + + +/** + * Handle a "/deposit" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_deposit (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif diff --git a/src/mint/taler-mint-httpd_keys.c b/src/mint/taler-mint-httpd_keys.c new file mode 100644 index 000000000..ba023fe69 --- /dev/null +++ b/src/mint/taler-mint-httpd_keys.c @@ -0,0 +1,512 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_keys.c + * @brief Handle /keys requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <libpq-fe.h> +#include <pthread.h> +#include "mint.h" +#include "mint_db.h" +#include "taler_types.h" +#include "taler_signatures.h" +#include "taler_rsa.h" +#include "taler_json_lib.h" +#include "taler_microhttpd_lib.h" +#include "taler-mint-httpd_keys.h" + + +/** + * Mint key state. Never use directly, instead access via + * #TALER_MINT_key_state_acquire and #TALER_MINT_key_state_release. + */ +static struct MintKeyState *internal_key_state; + +/** + * Mutex protecting access to #internal_key_state. + */ +static pthread_mutex_t internal_key_state_mutex = PTHREAD_MUTEX_INITIALIZER; + +/** + * Pipe used for signaling reloading of our key state. + */ +static int reload_pipe[2]; + + +/** + * Convert the public part of a denomination key + * issue to a JSON object. + * + * @param dki the denomination key issue + * @return a JSON object describing the denomination key isue (public part) + */ +static json_t * +denom_key_issue_to_json (const struct TALER_MINT_DenomKeyIssue *dki) +{ + json_t *dk_json = json_object (); + json_object_set_new (dk_json, "master_sig", + TALER_JSON_from_data (&dki->signature, sizeof (struct GNUNET_CRYPTO_EddsaSignature))); + json_object_set_new (dk_json, "stamp_start", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (dki->start))); + json_object_set_new (dk_json, "stamp_expire_withdraw", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (dki->expire_withdraw))); + json_object_set_new (dk_json, "stamp_expire_deposit", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (dki->expire_spend))); + json_object_set_new (dk_json, "denom_pub", + TALER_JSON_from_data (&dki->denom_pub, sizeof (struct TALER_RSA_PublicKeyBinaryEncoded))); + json_object_set_new (dk_json, "value", + TALER_JSON_from_amount (TALER_amount_ntoh (dki->value))); + json_object_set_new (dk_json, + "fee_withdraw", + TALER_JSON_from_amount(TALER_amount_ntoh (dki->fee_withdraw))); + json_object_set_new (dk_json, + "fee_deposit", + TALER_JSON_from_amount(TALER_amount_ntoh (dki->fee_deposit))); + json_object_set_new (dk_json, + "fee_refresh", + TALER_JSON_from_amount(TALER_amount_ntoh (dki->fee_refresh))); + return dk_json; +} + + +/** + * Convert the public part of a sign key + * issue to a JSON object. + * + * @param ski the sign key issue + * @return a JSON object describing the sign key isue (public part) + */ +static json_t * +sign_key_issue_to_json (const struct TALER_MINT_SignKeyIssue *ski) +{ + json_t *sk_json = json_object (); + json_object_set_new (sk_json, "stamp_start", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (ski->start))); + json_object_set_new (sk_json, "stamp_expire", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (ski->expire))); + json_object_set_new (sk_json, "master_sig", + TALER_JSON_from_data (&ski->signature, sizeof (struct GNUNET_CRYPTO_EddsaSignature))); + json_object_set_new (sk_json, "key", + TALER_JSON_from_data (&ski->signkey_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))); + return sk_json; +} + + +/** + * Get the relative time value that describes how + * far in the future do we want to provide coin keys. + * + * @return the provide duration + */ +static struct GNUNET_TIME_Relative +TALER_MINT_conf_duration_provide () +{ + struct GNUNET_TIME_Relative rel; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (cfg, + "mint_keys", + "lookahead_provide", + &rel)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "mint_keys.lookahead_provide not valid or not given\n"); + GNUNET_abort (); + } + return rel; +} + + +/** + * Iterator for denomination keys. + * + * @param cls closure + * @param dki the denomination key issue + * @param alias coin alias + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +static int +reload_keys_denom_iter (void *cls, + const char *alias, + const struct TALER_MINT_DenomKeyIssue *dki) +{ + struct MintKeyState *ctx = cls; + struct GNUNET_TIME_Absolute stamp_provide; + struct GNUNET_HashCode denom_key_hash; + int res; + + stamp_provide = GNUNET_TIME_absolute_add (ctx->reload_time, + TALER_MINT_conf_duration_provide ()); + + if (GNUNET_TIME_absolute_ntoh (dki->expire_spend).abs_value_us < ctx->reload_time.abs_value_us) + { + // this key is expired + return GNUNET_OK; + } + if (GNUNET_TIME_absolute_ntoh (dki->start).abs_value_us > stamp_provide.abs_value_us) + { + // we are to early for this key + return GNUNET_OK; + } + + GNUNET_CRYPTO_hash (&dki->denom_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey), &denom_key_hash); + + res = GNUNET_CONTAINER_multihashmap_put (ctx->denomkey_map, + &denom_key_hash, + GNUNET_memdup (dki, sizeof (struct TALER_MINT_DenomKeyIssue)), + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); + if (GNUNET_OK != res) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Duplicate denomination key\n"); + + json_array_append_new (ctx->denom_keys_array, + denom_key_issue_to_json (dki)); + + return GNUNET_OK; +} + + +/** + * Iterator for sign keys. + * + * @param cls closure + * @param ski the sign key issue + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +static int +reload_keys_sign_iter (void *cls, + const struct TALER_MINT_SignKeyIssue *ski) +{ + struct MintKeyState *ctx = cls; + struct GNUNET_TIME_Absolute stamp_provide; + + stamp_provide = GNUNET_TIME_absolute_add (ctx->reload_time, TALER_MINT_conf_duration_provide (cfg)); + + if (GNUNET_TIME_absolute_ntoh (ski->expire).abs_value_us < ctx->reload_time.abs_value_us) + { + // this key is expired + return GNUNET_OK; + } + + if (GNUNET_TIME_absolute_ntoh (ski->start).abs_value_us > stamp_provide.abs_value_us) + { + // we are to early for this key + return GNUNET_OK; + } + + // the signkey is valid for now, check + // if it's more recent than the current one! + if (GNUNET_TIME_absolute_ntoh (ctx->current_sign_key_issue.start).abs_value_us > + GNUNET_TIME_absolute_ntoh (ski->start).abs_value_us) + ctx->current_sign_key_issue = *ski; + + + ctx->next_reload = GNUNET_TIME_absolute_min (ctx->next_reload, + GNUNET_TIME_absolute_ntoh (ski->expire)); + + json_array_append_new (ctx->sign_keys_array, + sign_key_issue_to_json (ski)); + + return GNUNET_OK; +} + + +/** + * Load the mint's key state from disk. + * + * @return fresh key state (with reference count 1) + */ +static struct MintKeyState * +reload_keys () +{ + struct MintKeyState *key_state; + json_t *keys; + + key_state = GNUNET_new (struct MintKeyState); + key_state->refcnt = 1; + + key_state->next_reload = GNUNET_TIME_UNIT_FOREVER_ABS; + + key_state->denom_keys_array = json_array (); + GNUNET_assert (NULL != key_state->denom_keys_array); + + key_state->sign_keys_array = json_array (); + GNUNET_assert (NULL != key_state->sign_keys_array); + + key_state->denomkey_map = GNUNET_CONTAINER_multihashmap_create (32, GNUNET_NO); + GNUNET_assert (NULL != key_state->denomkey_map); + + key_state->reload_time = GNUNET_TIME_absolute_get (); + + TALER_MINT_denomkeys_iterate (mintdir, &reload_keys_denom_iter, key_state); + TALER_MINT_signkeys_iterate (mintdir, &reload_keys_sign_iter, key_state); + + keys = json_pack ("{s:o, s:o, s:o, s:o}", + "master_pub", TALER_JSON_from_data (&master_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)), + "signkeys", key_state->sign_keys_array, + "denoms", key_state->denom_keys_array, + "list_issue_date", TALER_JSON_from_abs (key_state->reload_time)); + + key_state->keys_json = json_dumps (keys, JSON_INDENT(2)); + + return key_state; +} + + +/** + * Release key state, free if necessary (if reference count gets to zero). + * + * @param key_state the key state to release + */ +void +TALER_MINT_key_state_release (struct MintKeyState *key_state) +{ + GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex)); + GNUNET_assert (0 != key_state->refcnt); + key_state->refcnt += 1; + if (key_state->refcnt == 0) { + GNUNET_free (key_state); + } + GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex)); +} + + +/** + * Acquire the key state of the mint. Updates keys if necessary. + * For every call to #TALER_MINT_key_state_acquire, a matching call + * to #TALER_MINT_key_state_release must be made. + * + * @return the key state + */ +struct MintKeyState * +TALER_MINT_key_state_acquire (void) +{ + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct MintKeyState *key_state; + + // FIXME: the locking we have is very coarse-grained, + // using multiple locks might be nicer ... + + GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex)); + if (NULL == internal_key_state) + { + internal_key_state = reload_keys (); + } + else if (internal_key_state->next_reload.abs_value_us <= now.abs_value_us) + { + GNUNET_assert (0 != internal_key_state->refcnt); + internal_key_state->refcnt--; + if (0 == internal_key_state->refcnt) + GNUNET_free (internal_key_state); + internal_key_state = reload_keys (); + } + key_state = internal_key_state; + key_state->refcnt += 1; + GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex)); + + return key_state; +} + + +/** + * Look up the issue for a denom public key. + * + * @param key state to look in + * @param denom_pub denomination public key + * @return the denomination key issue, + * or NULL if denom_pub could not be found + */ +struct TALER_MINT_DenomKeyIssue * +TALER_MINT_get_denom_key (const struct MintKeyState *key_state, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub) +{ + struct TALER_MINT_DenomKeyIssue *issue; + struct GNUNET_HashCode hash; + + GNUNET_CRYPTO_hash (denom_pub, sizeof (struct TALER_RSA_PublicKeyBinaryEncoded), &hash); + issue = GNUNET_CONTAINER_multihashmap_get (key_state->denomkey_map, &hash); + return issue; +} + + +/** + * Check if a coin is valid; that is, whether the denomination key exists, + * is not expired, and the signature is correct. + * + * @param key_state the key state to use for checking the coin's validity + * @param coin_public_info the coin public info to check for validity + * @return GNUNET_YES if the coin is valid, + * GNUNET_NO if it is invalid + * GNUNET_SYSERROR if an internal error occured + */ +int +TALER_MINT_test_coin_valid (const struct MintKeyState *key_state, + struct TALER_CoinPublicInfo *coin_public_info) +{ + struct TALER_MINT_DenomKeyIssue *dki; + + dki = TALER_MINT_get_denom_key (key_state, &coin_public_info->denom_pub); + if (NULL == dki) + return GNUNET_NO; + if (GNUNET_OK != TALER_RSA_verify (&coin_public_info->coin_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), + &coin_public_info->denom_sig, + &dki->denom_pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "coin signature is invalid\n"); + return GNUNET_NO; + } + return GNUNET_YES; +} + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_keys (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct MintKeyState *key_state; + struct MHD_Response *response; + int ret; + + key_state = TALER_MINT_key_state_acquire (); + response = MHD_create_response_from_buffer (strlen (key_state->keys_json), + key_state->keys_json, + MHD_RESPMEM_MUST_COPY); + TALER_MINT_key_state_release (key_state); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + (void) MHD_add_response_header (response, + "Content-Type", + rh->mime_type); + ret = MHD_queue_response (connection, + rh->response_code, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Handle a signal, writing relevant signal numbers + * (currently just SIGUSR1) to a pipe. + * + * @param signal_number the signal number + */ +static void +handle_signal (int signal_number) +{ + size_t res; + char c = signal_number; + + if (SIGUSR1 == signal_number) + { + errno = 0; + res = write (reload_pipe[1], &c, 1); + if ((res < 0) && (EINTR != errno)) + { + GNUNET_break (0); + return; + } + if (0 == res) + { + GNUNET_break (0); + return; + } + } +} + + +/** + * Read signals from a pipe in a loop, and reload keys from disk if + * SIGUSR1 is read from the pipe. + */ +int +TALER_MINT_key_reload_loop (void) +{ + struct sigaction act; + + if (0 != pipe (reload_pipe)) + { + fprintf (stderr, + "Failed to create pipe.\n"); + return GNUNET_SYSERR; + } + memset (&act, 0, sizeof (struct sigaction)); + act.sa_handler = &handle_signal; + + if (0 != sigaction (SIGUSR1, &act, NULL)) + { + fprintf (stderr, + "Failed to set signal handler.\n"); + return GNUNET_SYSERR; + } + + while (1) + { + char c; + ssize_t res; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "(re-)loading keys\n"); + GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex)); + if (NULL != internal_key_state) + { + GNUNET_assert (0 != internal_key_state->refcnt); + internal_key_state->refcnt -= 1; + if (0 == internal_key_state->refcnt) + GNUNET_free (internal_key_state); + } + internal_key_state = reload_keys (); + GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex)); +read_again: + errno = 0; + res = read (reload_pipe[0], &c, 1); + if ((res < 0) && (EINTR != errno)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (EINTR == errno) + goto read_again; + } + return GNUNET_OK; +} + + +/* end of taler-mint-httpd_keys.c */ diff --git a/src/mint/taler-mint-httpd_keys.h b/src/mint/taler-mint-httpd_keys.h new file mode 100644 index 000000000..640a9c916 --- /dev/null +++ b/src/mint/taler-mint-httpd_keys.h @@ -0,0 +1,155 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_keys.h + * @brief Handle /keys requests and manage key state + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_KEYS_H +#define TALER_MINT_HTTPD_KEYS_H + +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + + +/** + * Snapshot of the (coin and signing) + * keys (including private keys) of the mint. + */ +struct MintKeyState +{ + /** + * When did we initiate the key reloading? + */ + struct GNUNET_TIME_Absolute reload_time; + + /** + * JSON array with denomination keys. + */ + json_t *denom_keys_array; + + /** + * JSON array with signing keys. + */ + json_t *sign_keys_array; + + /** + * Mapping from denomination keys to denomination key issue struct. + */ + struct GNUNET_CONTAINER_MultiHashMap *denomkey_map; + + /** + * When is the next key invalid and we have to reload? + */ + struct GNUNET_TIME_Absolute next_reload; + + /** + * Mint signing key that should be used currently. + */ + struct TALER_MINT_SignKeyIssue current_sign_key_issue; + + /** + * Cached JSON text that the mint will send for + * a /keys request. + */ + char *keys_json; + + /** + * Reference count. + */ + unsigned int refcnt; +}; + + +/** + * Release key state, free if necessary (if reference count gets to zero). + * + * @param key_state the key state to release + */ +void +TALER_MINT_key_state_release (struct MintKeyState *key_state); + + +/** + * Acquire the key state of the mint. Updates keys if necessary. + * For every call to #TALER_MINT_key_state_acquire, a matching call + * to #TALER_MINT_key_state_release must be made. + * + * @return the key state + */ +struct MintKeyState * +TALER_MINT_key_state_acquire (void); + + +/** + * Look up the issue for a denom public key. + * + * @param key state to look in + * @param denom_pub denomination public key + * @return the denomination key issue, + * or NULL if denom_pub could not be found + */ +struct TALER_MINT_DenomKeyIssue * +TALER_MINT_get_denom_key (const struct MintKeyState *key_state, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub); + + +/** + * Check if a coin is valid; that is, whether the denomination key exists, + * is not expired, and the signature is correct. + * + * @param key_state the key state to use for checking the coin's validity + * @param coin_public_info the coin public info to check for validity + * @return GNUNET_YES if the coin is valid, + * GNUNET_NO if it is invalid + * GNUNET_SYSERROR if an internal error occured + */ +int +TALER_MINT_test_coin_valid (const struct MintKeyState *key_state, + struct TALER_CoinPublicInfo *coin_public_info); + + +/** + * Read signals from a pipe in a loop, and reload keys from disk if + * SIGUSR1 is read from the pipe. + * + * @return GNUNET_OK if we terminated normally, GNUNET_SYSERR on error + */ +int +TALER_MINT_key_reload_loop (void); + + +/** + * Handle a "/keys" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_keys (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif diff --git a/src/mint/taler-mint-httpd_mhd.c b/src/mint/taler-mint-httpd_mhd.c new file mode 100644 index 000000000..09f3025b8 --- /dev/null +++ b/src/mint/taler-mint-httpd_mhd.c @@ -0,0 +1,300 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd_mhd.c + * @brief helpers for MHD interaction + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <libpq-fe.h> +#include <pthread.h> +#include "taler_microhttpd_lib.h" +#include "taler-mint-httpd.h" +#include "taler-mint-httpd_mhd.h" + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_static_response (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct MHD_Response *response; + int ret; + + if (0 == rh->data_size) + rh->data_size = strlen ((const char *) rh->data); + response = MHD_create_response_from_buffer (rh->data_size, + (void *) rh->data, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + if (NULL != rh->mime_type) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + rh->mime_type); + ret = MHD_queue_response (connection, + rh->response_code, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_agpl_redirect (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + const char *agpl = + "This server is licensed under the Affero GPL. You will now be redirected to the source code."; + struct MHD_Response *response; + int ret; + + response = MHD_create_response_from_buffer (strlen (agpl), + (void *) agpl, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + if (NULL != rh->mime_type) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + rh->mime_type); + MHD_add_response_header (response, + MHD_HTTP_HEADER_LOCATION, + "http://www.git.taler.net/?p=mint.git"); + ret = MHD_queue_response (connection, + rh->response_code, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Function to call to handle the request by building a JSON + * reply from varargs. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param response_code HTTP response code to use + * @param do_cache can the response be cached? (0: no, 1: yes) + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TALER_MINT_helper_send_json_pack (struct RequestHandler *rh, + struct MHD_Connection *connection, + void *connection_cls, + int response_code, + int do_cache, + const char *fmt, + ...) +{ + int ret; + json_t *json; + va_list argp; + char *json_str; + struct MHD_Response *response; + + va_start (argp, fmt); + json = json_vpack_ex (NULL, 0, fmt, argp); + va_end (argp); + if (NULL == json) + return MHD_NO; + json_str = json_dumps (json, JSON_INDENT(2)); + json_decref (json); + if (NULL == json_str) + return MHD_NO; + response = MHD_create_response_from_buffer (strlen (json_str), + json_str, + MHD_RESPMEM_MUST_FREE); + if (NULL == response) + { + free (json_str); + return MHD_NO; + } + if (NULL != rh->mime_type) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + rh->mime_type); + ret = MHD_queue_response (connection, + response_code, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Function to call to handle the request by building a JSON + * reply with an error message from @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_send_json_pack_error (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + return TALER_MINT_helper_send_json_pack (rh, + connection, + connection_cls, + 1, /* caching enabled */ + rh->response_code, + "{s:s}", + "error", + rh->data); +} + + +/** + * Send a response for an invalid argument. + * + * @param connection the MHD connection to use + * @param param_name the parameter that is missing + * @return a GNUnet result code + */ +static int +request_arg_invalid (struct MHD_Connection *connection, + const char *param_name) +{ + json_t *json; + json = json_pack ("{ s:s, s:s }", + "error", "invalid parameter", + "parameter", param_name); + if (MHD_YES != send_response_json (connection, json, MHD_HTTP_BAD_REQUEST)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_NO; +} + + +/** + * Get a GET paramater that is a string, + * or send an error response if the parameter is missing. + * + * @param connection the connection to get the parameter from / + * send the error response to + * @param param_name the parameter name + * @param str pointer to store the parameter string, + * must be freed by the caller + * @return GNUNET_YES if the parameter is present and valid, + * GNUNET_NO if the parameter is missing + * GNUNET_SYSERR on internal error + */ +static int +request_arg_require_string (struct MHD_Connection *connection, + const char *param_name, + const char **str) +{ + *str = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, param_name); + if (NULL == *str) + { + if (MHD_NO == + request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + "{ s:s, s:s }", + "error", "missing parameter", + "parameter", param_name)) + return GNUNET_SYSERR; + return GNUNET_NO; + } + return GNUNET_OK; +} + + +/** + * Extraxt base32crockford encoded data from request. + * + * Queues an error response to the connection if the parameter is missing or + * invalid. + * + * @param connection the MHD connection + * @param param_name the name of the parameter with the key + * @param[out] out_data pointer to store the result + * @param out_size expected size of data + * @return + * GNUNET_YES if the the argument is present + * GNUNET_NO if the argument is absent or malformed + * GNUNET_SYSERR on internal error (error response could not be sent) + */ +int +TALER_MINT_mhd_request_arg_data (struct MHD_Connection *connection, + const char *param_name, + void *out_data, + size_t out_size) +{ + const char *str; + int ret; + + if (GNUNET_OK != (ret = request_arg_require_string (connection, param_name, &str))) + return ret; + if (GNUNET_OK != GNUNET_STRINGS_string_to_data (str, strlen (str), out_data, out_size)) + return request_arg_invalid (connection, param_name); + return GNUNET_OK; +} + + + +/* end of taler-mint-httpd_mhd.c */ diff --git a/src/mint/taler-mint-httpd_mhd.h b/src/mint/taler-mint-httpd_mhd.h new file mode 100644 index 000000000..29ab7f64b --- /dev/null +++ b/src/mint/taler-mint-httpd_mhd.h @@ -0,0 +1,132 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd_mhd.h + * @brief helpers for MHD interaction + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_MHD_H +#define TALER_MINT_HTTPD_MHD_H +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_static_response (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_agpl_redirect (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Function to call to handle the request by building a JSON + * reply from varargs. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param response_code HTTP response code to use + * @param do_cache can the response be cached? (0: no, 1: yes) + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TALER_MINT_helper_send_json_pack (struct RequestHandler *rh, + struct MHD_Connection *connection, + void *connection_cls, + int response_code, + int do_cache, + const char *fmt, + ...); + + +/** + * Function to call to handle the request by building a JSON + * reply with an error message from @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_send_json_pack_error (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Extraxt base32crockford encoded data from request. + * + * Queues an error response to the connection if the parameter is missing or + * invalid. + * + * @param connection the MHD connection + * @param param_name the name of the parameter with the key + * @param[out] out_data pointer to store the result + * @param out_size expected size of data + * @return + * GNUNET_YES if the the argument is present + * GNUNET_NO if the argument is absent or malformed + * GNUNET_SYSERR on internal error (error response could not be sent) + */ +int +TALER_MINT_mhd_request_arg_data (struct MHD_Connection *connection, + const char *param_name, + void *out_data, + size_t out_size); + +#endif diff --git a/src/mint/taler-mint-httpd_refresh.c b/src/mint/taler-mint-httpd_refresh.c new file mode 100644 index 000000000..8121bb234 --- /dev/null +++ b/src/mint/taler-mint-httpd_refresh.c @@ -0,0 +1,1497 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_refresh.c + * @brief Handle /refresh/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <libpq-fe.h> +#include <pthread.h> +#include "mint.h" +#include "mint_db.h" +#include "taler_types.h" +#include "taler_signatures.h" +#include "taler_rsa.h" +#include "taler_json_lib.h" +#include "taler_microhttpd_lib.h" +#include "taler-mint-httpd_keys.h" +#include "taler-mint-httpd_mhd.h" +#include "taler-mint-httpd_refresh.h" + + +/** + * Sign the message in @a purpose with the mint's signing + * key and encode the signature as a JSON object. + * + * @param purpose the message to sign + * @return signature as JSON object + */ +static json_t * +sign_as_json (struct GNUNET_CRYPTO_EccSignaturePurpose *purpose) +{ + json_t *sig_json; + struct GNUNET_CRYPTO_EddsaSignature sig; + struct MintKeyState *key_state; + + key_state = TALER_MINT_key_state_acquire (); + + sig_json = json_object (); + + GNUNET_assert (GNUNET_OK == GNUNET_CRYPTO_eddsa_sign (&key_state->current_sign_key_issue.signkey_priv, + purpose, + &sig)); + + TALER_MINT_key_state_release (key_state); + + json_object_set (sig_json, "sig", TALER_JSON_from_data (&sig, sizeof (struct GNUNET_CRYPTO_EddsaSignature))); + json_object_set (sig_json, "purpose", json_integer (ntohl (purpose->purpose))); + json_object_set (sig_json, "size", json_integer (ntohl (purpose->size))); + + return sig_json; +} + + +static int +link_iter (void *cls, + const struct LinkDataEnc *link_data_enc, + const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub, + const struct TALER_RSA_Signature *ev_sig) +{ + json_t *list = cls; + json_t *obj = json_object (); + + json_array_append_new (list, obj); + + json_object_set_new (obj, "link_enc", + TALER_JSON_from_data (link_data_enc, + sizeof (struct LinkDataEnc))); + + json_object_set_new (obj, "denom_pub", + TALER_JSON_from_data (denom_pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded))); + + json_object_set_new (obj, "ev_sig", + TALER_JSON_from_data (ev_sig, + sizeof (struct TALER_RSA_Signature))); + + return GNUNET_OK; +} + + +/** + * Insert all requested denominations into the db, and compute the + * required cost of the denominations, including fees. + * + * @param connection the connection to send an error response to + * @param db_conn the database connection + * @param key_state the mint's key state to use + * @param session_pub the refresh session public key + * @param root the request JSON object + * @param hash_context the hash context where accepted + * denominations will be hased into + * @param r_amount the sum of the cost (value+fee) for + * all requested coins + * @return FIXME! + */ +static int +refresh_accept_denoms (struct MHD_Connection *connection, + PGconn *db_conn, + const struct MintKeyState *key_state, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const json_t *root, + struct TALER_HashContext *hash_context, + struct TALER_Amount *r_amount) +{ + unsigned i; + int res; + json_t *new_denoms; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "new_denoms", + JNAV_RET_TYPED_JSON, + JSON_ARRAY, + &new_denoms); + if (GNUNET_OK != res) + return res; + + memset (r_amount, 0, sizeof (struct TALER_Amount)); + + for (i = 0; i < json_array_size (new_denoms); i++) + { + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + int res; + struct TALER_MINT_DenomKeyIssue *dki; + struct TALER_Amount cost; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "new_denoms", + JNAV_INDEX, (int) i, + JNAV_RET_DATA, + &denom_pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)); + + if (GNUNET_OK != res) + return res; + + dki = TALER_MINT_get_denom_key (key_state, &denom_pub); + + TALER_hash_context_read (hash_context, + &denom_pub, sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)); + + cost = TALER_amount_add (TALER_amount_ntoh (dki->value), + TALER_amount_ntoh (dki->fee_withdraw)); + + *r_amount = TALER_amount_add (cost, *r_amount); + + /* Insert the requested coin into the DB, so we'll know later + * what denomination the request had */ + + if (GNUNET_OK != + TALER_MINT_DB_insert_refresh_order (db_conn, + i, + session_pub, + &denom_pub)) + return res; // ??? + } + return GNUNET_OK; +} + + +/** + * Get an amount in the mint's currency + * that is zero. + * + * @return zero amount in the mint's currency + */ +static struct TALER_Amount +mint_amount_native_zero () +{ + struct TALER_Amount amount; + + memset (&amount, 0, sizeof (amount)); + // FIXME: load from config + memcpy (amount.currency, "EUR", 3); + + return amount; +} + + +static int +check_confirm_signature (struct MHD_Connection *connection, + json_t *coin_info, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub) +{ + struct RefreshMeltConfirmSignRequestBody body; + struct GNUNET_CRYPTO_EcdsaSignature sig; + int res; + + body.purpose.size = htonl (sizeof (struct RefreshMeltConfirmSignRequestBody)); + body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT_CONFIRM); + body.session_pub = *session_pub; + + res = request_json_require_nav (connection, coin_info, + JNAV_FIELD, "confirm_sig", + JNAV_RET_DATA, + &sig, + sizeof (struct GNUNET_CRYPTO_EcdsaSignature)); + + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + return res; + } + + if (GNUNET_OK != + GNUNET_CRYPTO_ecdsa_verify (TALER_SIGNATURE_REFRESH_MELT_CONFIRM, + &body.purpose, + &sig, + coin_pub)) + { + if (MHD_YES != + request_send_json_pack (connection, + MHD_HTTP_UNAUTHORIZED, + "{s:s}", + "error", "signature invalid")) + return GNUNET_SYSERR; + return GNUNET_NO; + } + + return GNUNET_OK; +} + + +/** + * Extract public coin information from a JSON object. + * + * @param connection the connection to send error responses to + * @param root the JSON object to extract the coin info from + * @return GNUNET_YES if coin public info in JSON was valid + * GNUNET_NO otherwise + * GNUNET_SYSERR on internal error + */ +static int +request_json_require_coin_public_info (struct MHD_Connection *connection, + json_t *root, + struct TALER_CoinPublicInfo *r_public_info) +{ + int ret; + + GNUNET_assert (NULL != root); + + ret = request_json_require_nav (connection, root, + JNAV_FIELD, "coin_pub", + JNAV_RET_DATA, + &r_public_info->coin_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); + if (GNUNET_OK != ret) + return ret; + + ret = request_json_require_nav (connection, root, + JNAV_FIELD, "denom_sig", + JNAV_RET_DATA, + &r_public_info->denom_sig, + sizeof (struct TALER_RSA_Signature)); + if (GNUNET_OK != ret) + return ret; + + ret = request_json_require_nav (connection, root, + JNAV_FIELD, "denom_pub", + JNAV_RET_DATA, + &r_public_info->denom_pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)); + if (GNUNET_OK != ret) + return ret; + + return GNUNET_OK; +} + + +/** + * Parse coin melt requests from a JSON object and write them to + * the database. + * + * @param connection the connection to send errors to + * @param db_conn the database connection + * @param key_state the mint's key state + * @param session_pub the refresh session's public key + * @param root the JSON object + * @param hash_context the hash context that will receive + * the coin public keys of the melted coin + * @return a GNUnet result code, GNUNET_OK on success, + * GNUNET_NO if an error message was generated, + * GNUNET_SYSERR on internal errors (no response generated) + */ +static int +refresh_accept_melts (struct MHD_Connection *connection, + PGconn *db_conn, + const struct MintKeyState *key_state, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + json_t *root, + struct TALER_HashContext *hash_context, + struct TALER_Amount *r_melt_balance) +{ + size_t i; + int res; + json_t *melt_coins; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "melt_coins", + JNAV_RET_TYPED_JSON, + JSON_ARRAY, + &melt_coins); + if (GNUNET_OK != res) + return res; + + memset (r_melt_balance, 0, sizeof (struct TALER_Amount)); + + for (i = 0; i < json_array_size (melt_coins); i++) + { + struct TALER_CoinPublicInfo coin_public_info; + struct TALER_MINT_DenomKeyIssue *dki; + struct KnownCoin known_coin; + // money the customer gets by melting the current coin + struct TALER_Amount coin_gain; + + res = request_json_require_coin_public_info (connection, + json_array_get (melt_coins, i), + &coin_public_info); + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + return res; + } + + if (GNUNET_OK != (res = check_confirm_signature (connection, + json_array_get (melt_coins, i), + &coin_public_info.coin_pub, + session_pub))) + { + GNUNET_break (GNUNET_SYSERR != res); + return res; + } + + TALER_hash_context_read (hash_context, + &coin_public_info.coin_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + + dki = TALER_MINT_get_denom_key (key_state, &coin_public_info.denom_pub); + + if (NULL == dki) + return (MHD_YES == request_send_json_pack (connection, MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "denom not found")) + ? GNUNET_NO : GNUNET_SYSERR; + + if (GNUNET_OK != TALER_MINT_test_coin_valid (key_state, &coin_public_info)) + return (MHD_YES == request_send_json_pack (connection, MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "coin invalid")) + ? GNUNET_NO : GNUNET_SYSERR; + + res = TALER_MINT_DB_get_known_coin (db_conn, &coin_public_info.coin_pub, + &known_coin); + + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_YES == res) + { + if (GNUNET_YES == known_coin.is_refreshed) + return (MHD_YES == request_send_json_pack (connection, MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "coin already refreshed")) ? GNUNET_NO : GNUNET_SYSERR; + } + else + { + known_coin.expended_balance = mint_amount_native_zero (); + known_coin.public_info = coin_public_info; + } + + known_coin.is_refreshed = GNUNET_YES; + known_coin.refresh_session_pub = *session_pub; + + if (GNUNET_OK != TALER_MINT_DB_upsert_known_coin (db_conn, &known_coin)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != TALER_MINT_DB_insert_refresh_melt (db_conn, session_pub, i, + &coin_public_info.coin_pub, + &coin_public_info.denom_pub)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + coin_gain = TALER_amount_ntoh (dki->value); + coin_gain = TALER_amount_subtract (coin_gain, known_coin.expended_balance); + + /* Refuse to refresh when the coin does not have enough money left to + * pay the refreshing fees of the coin. */ + + if (TALER_amount_cmp (coin_gain, TALER_amount_ntoh (dki->fee_refresh)) < 0) + return (MHD_YES == request_send_json_pack (connection, MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "depleted")) ? GNUNET_NO : GNUNET_SYSERR; + + coin_gain = TALER_amount_subtract (coin_gain, TALER_amount_ntoh (dki->fee_refresh)); + + *r_melt_balance = TALER_amount_add (*r_melt_balance, coin_gain); + } + return GNUNET_OK; +} + + +/** + * Send a response for "/refresh/melt". + * + * @param connection the connection to send the response to + * @param db_conn the database connection to fetch values from + * @param session_pub the refresh session public key. + * @return a MHD result code + */ +static int +helper_refresh_send_melt_response (struct MHD_Connection *connection, + PGconn *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub) +{ + struct RefreshSession session; + int res; + json_t *root; + json_t *list; + struct TALER_HashContext hash_context; + + if (GNUNET_OK != + (res = TALER_MINT_DB_get_refresh_session (db_conn, + session_pub, + &session))) + { + // FIXME: send internal error + GNUNET_break (0); + return MHD_NO; + } + + root = json_object (); + list = json_array (); + json_object_set_new (root, "blind_session_pubs", list); + + TALER_hash_context_start (&hash_context); + + { + struct RefreshMeltResponseSignatureBody body; + json_t *sig_json; + + body.purpose.size = htonl (sizeof (struct RefreshMeltResponseSignatureBody)); + body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT_RESPONSE); + TALER_hash_context_finish (&hash_context, &body.melt_response_hash); + sig_json = sign_as_json (&body.purpose); + GNUNET_assert (NULL != sig_json); + json_object_set (root, "signature", sig_json); + } + + return send_response_json (connection, + root, + MHD_HTTP_OK); +} + + +/** + * Verify a signature that is encoded in a JSON object + * + * @param connection the connection to send errors to + * @param root the JSON object with the signature + * @param the public key that the signature was created with + * @param purpose the signed message + * @return GNUNET_YES if the signature was valid + * GNUNET_NO if the signature was invalid + * GNUNET_SYSERR on internal error + */ +static int +request_json_check_signature (struct MHD_Connection *connection, + json_t *root, + struct GNUNET_CRYPTO_EddsaPublicKey *pub, + struct GNUNET_CRYPTO_EccSignaturePurpose *purpose) +{ + struct GNUNET_CRYPTO_EddsaSignature signature; + int size; + uint32_t purpose_num; + int res; + json_t *el; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "sig", + JNAV_RET_DATA, + &signature, + sizeof (struct GNUNET_CRYPTO_EddsaSignature)); + + if (GNUNET_OK != res) + return res; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "purpose", + JNAV_RET_TYPED_JSON, + JSON_INTEGER, + &el); + + if (GNUNET_OK != res) + return res; + + purpose_num = json_integer_value (el); + + if (purpose_num != ntohl (purpose->purpose)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "signature invalid (purpose wrong)\n"); + return request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + "{s:s}", + "error", "signature invalid (purpose)"); + } + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "size", + JNAV_RET_TYPED_JSON, + JSON_INTEGER, + &el); + + if (GNUNET_OK != res) + return res; + + size = json_integer_value (el); + + if (size != ntohl (purpose->size)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "signature invalid (size wrong)\n"); + return request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + GNUNET_NO, GNUNET_SYSERR, + "{s:s}", + "error", "signature invalid (size)"); + } + + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (purpose_num, + purpose, + &signature, + pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "signature invalid (did not verify)\n"); + return request_send_json_pack (connection, MHD_HTTP_UNAUTHORIZED, + "{s:s}", + "error", "invalid signature (verification)"); + } + + return GNUNET_OK; +} + + +/** + * Handle a "/refresh/melt" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_melt (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + json_t *root; + PGconn *db_conn; + struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub; + int res; + struct MintKeyState *key_state; + struct TALER_Amount requested_cost; + struct TALER_Amount melt_balance; + struct TALER_HashContext hash_context; + struct GNUNET_HashCode melt_hash; + + res = process_post_json (connection, + connection_cls, + upload_data, + upload_data_size, &root); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + return MHD_YES; + + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + return GNUNET_SYSERR; + + /* session_pub field must always be present */ + res = request_json_require_nav (connection, root, + JNAV_FIELD, "session_pub", + JNAV_RET_DATA, + &refresh_session_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + if (GNUNET_OK != res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + return MHD_YES; + + /* Send response immediately if we already know the session. + * Do _not_ care about fields other than session_pub in this case. */ + + res = TALER_MINT_DB_get_refresh_session (db_conn, + &refresh_session_pub, + NULL); + if (GNUNET_YES == res) + return helper_refresh_send_melt_response (connection, + db_conn, + &refresh_session_pub); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + /* We incrementally update the db with other parameters in a transaction. + * The transaction is aborted if some parameter does not validate. */ + + if (GNUNET_OK != TALER_MINT_DB_transaction (db_conn)) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + if (GNUNET_OK != TALER_MINT_DB_create_refresh_session (db_conn, + &refresh_session_pub)) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + TALER_MINT_DB_rollback (db_conn); + return MHD_NO; + } + + /* The next two operations must see the same key state, + * thus we acquire it here. */ + + key_state = TALER_MINT_key_state_acquire (); + + /* Write requested denominations to the DB, + * and sum the costs (value plus fees) */ + + TALER_hash_context_start (&hash_context); + + if (GNUNET_OK != (res = refresh_accept_denoms (connection, db_conn, key_state, + &refresh_session_pub, root, + &hash_context, + &requested_cost))) + { + TALER_MINT_key_state_release (key_state); + TALER_MINT_DB_rollback (db_conn); + // FIXME: hash_context_end? + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + /* Write old coins to db and sum their value */ + + if (GNUNET_OK != (res = refresh_accept_melts (connection, db_conn, key_state, + &refresh_session_pub, root, + &hash_context, + &melt_balance))) + { + TALER_MINT_key_state_release (key_state); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + TALER_hash_context_finish (&hash_context, &melt_hash); + + TALER_MINT_key_state_release (key_state); + + /* check that signature from the session public key is ok */ + { + struct RefreshMeltSignatureBody body; + json_t *melt_sig_json; + + melt_sig_json = json_object_get (root, "melt_signature"); + if (NULL == melt_sig_json) + { + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return request_send_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s}", + "error", "melt_signature missing"); + } + + body.melt_hash = melt_hash; + body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT); + body.purpose.size = htonl (sizeof (struct RefreshMeltSignatureBody)); + + if (GNUNET_OK != (res = request_json_check_signature (connection, + melt_sig_json, + &refresh_session_pub, + &body.purpose))) + { + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + } + + + /* Request is only ok if cost of requested coins + * does not exceed value of melted coins. */ + + // FIXME: also, consider fees? + if (TALER_amount_cmp (melt_balance, requested_cost) < 0) + { + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + + return request_send_json_pack (connection, MHD_HTTP_FORBIDDEN, + "{s:s}", + "error", "not enough coins melted"); + } + + if (GNUNET_OK != TALER_MINT_DB_commit (db_conn)) + { + GNUNET_break (0); + return MHD_NO; + } + return helper_refresh_send_melt_response (connection, + db_conn, + &refresh_session_pub); +} + + +/** + * Send a response to a "/refresh/commit" request. + * + * @param connection the connection to send the response to + * @param db_conn the mint database + * @param refresh_session the refresh session + * @return a MHD status code + */ +static int +refresh_send_commit_response (struct MHD_Connection *connection, + PGconn *db_conn, + struct RefreshSession *refresh_session) +{ + struct RefreshCommitResponseSignatureBody body; + json_t *sig_json; + + body.purpose.size = htonl (sizeof (struct RefreshCommitResponseSignatureBody)); + body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_COMMIT_RESPONSE); + body.noreveal_index = htons (refresh_session->noreveal_index); + sig_json = sign_as_json (&body.purpose); + GNUNET_assert (NULL != sig_json); + return request_send_json_pack (connection, MHD_HTTP_OK, + "{s:i, s:o}", + "noreveal_index", (int) refresh_session->noreveal_index, + "signature", sig_json); +} + + +/** + * Handle a "/refresh/commit" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_commit (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub; + int res; + PGconn *db_conn; + struct RefreshSession refresh_session; + int i; + struct GNUNET_HashCode commit_hash; + struct TALER_HashContext hash_context; + json_t *root; + + res = process_post_json (connection, + connection_cls, + upload_data, + upload_data_size, &root); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + return MHD_YES; + + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "session_pub", + JNAV_RET_DATA, + &refresh_session_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + /* Send response immediately if we already know the session. + * Do _not_ care about fields other than session_pub in this case. */ + + res = TALER_MINT_DB_get_refresh_session (db_conn, + &refresh_session_pub, + &refresh_session); + if ( (GNUNET_YES == res) && + (GNUNET_YES == refresh_session.has_commit_sig) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "sending cached commit response\n"); + res = refresh_send_commit_response (connection, + db_conn, + &refresh_session); + GNUNET_break (res != GNUNET_SYSERR); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + if (GNUNET_OK != TALER_MINT_DB_transaction (db_conn)) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + /* Re-fetch the session information from the database, + * in case a concurrent transaction modified it. */ + + res = TALER_MINT_DB_get_refresh_session (db_conn, + &refresh_session_pub, + &refresh_session); + if (GNUNET_OK != res) + { + // FIXME: return 'internal error'? + GNUNET_break (GNUNET_SYSERR != res); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return MHD_NO; + } + + TALER_hash_context_start (&hash_context); + + for (i = 0; i < refresh_session.kappa; i++) + { + unsigned int j; + + for (j = 0; j < refresh_session.num_newcoins; j++) + { + struct RefreshCommitCoin commit_coin; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "coin_evs", + JNAV_INDEX, (int) i, + JNAV_INDEX, (int) j, + JNAV_RET_DATA, + &commit_coin.coin_ev, + sizeof (struct TALER_RSA_BlindedSignaturePurpose)); + + if (GNUNET_OK != res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + TALER_hash_context_read (&hash_context, + &commit_coin.coin_ev, + sizeof (struct TALER_RSA_BlindedSignaturePurpose)); + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "link_encs", + JNAV_INDEX, (int) i, + JNAV_INDEX, (int) j, + JNAV_RET_DATA, + commit_coin.link_enc, + TALER_REFRESH_LINK_LENGTH); + if (GNUNET_OK != res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + TALER_hash_context_read (&hash_context, + commit_coin.link_enc, + TALER_REFRESH_LINK_LENGTH); + + commit_coin.cnc_index = i; + commit_coin.newcoin_index = j; + commit_coin.session_pub = refresh_session_pub; + + if (GNUNET_OK != + TALER_MINT_DB_insert_refresh_commit_coin (db_conn, + &commit_coin)) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return MHD_NO; + } + } + } + + for (i = 0; i < refresh_session.kappa; i++) + { + unsigned int j; + for (j = 0; j < refresh_session.num_oldcoins; j++) + { + struct RefreshCommitLink commit_link; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "transfer_pubs", + JNAV_INDEX, (int) i, + JNAV_INDEX, (int) j, + JNAV_RET_DATA, + &commit_link.transfer_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); + + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + TALER_hash_context_read (&hash_context, + &commit_link.transfer_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "secret_encs", + JNAV_INDEX, (int) i, + JNAV_INDEX, (int) j, + JNAV_RET_DATA, + commit_link.shared_secret_enc, + TALER_REFRESH_SHARED_SECRET_LENGTH); + + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + TALER_hash_context_read (&hash_context, + commit_link.shared_secret_enc, + TALER_REFRESH_SHARED_SECRET_LENGTH); + + commit_link.cnc_index = i; + commit_link.oldcoin_index = j; + commit_link.session_pub = refresh_session_pub; + + if (GNUNET_OK != TALER_MINT_DB_insert_refresh_commit_link (db_conn, &commit_link)) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return MHD_NO; + } + } + } + + TALER_hash_context_finish (&hash_context, &commit_hash); + + { + struct RefreshCommitSignatureBody body; + json_t *commit_sig_json; + + commit_sig_json = json_object_get (root, "commit_signature"); + if (NULL == commit_sig_json) + { + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + "{s:s}", + "error", "commit_signature missing"); + } + + body.commit_hash = commit_hash; + + body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_COMMIT); + body.purpose.size = htonl (sizeof (struct RefreshCommitSignatureBody)); + + if (GNUNET_OK != (res = request_json_check_signature (connection, + commit_sig_json, + &refresh_session_pub, + &body.purpose))) + { + GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn)); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + } + + if (GNUNET_OK != TALER_MINT_DB_commit (db_conn)) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + return refresh_send_commit_response (connection, db_conn, &refresh_session); +} + + +/** + * Send response for "/refresh/reveal". + * + * @param connection the MHD connection + * @param db_conn the connection to the mint's db + * @param refresh_session_pub the refresh session's public key + * @return a MHD result code + */ +static int +helper_refresh_reveal_send_response (struct MHD_Connection *connection, + PGconn *db_conn, + struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub) +{ + int res; + int newcoin_index; + struct RefreshSession refresh_session; + json_t *root; + json_t *list; + + res = TALER_MINT_DB_get_refresh_session (db_conn, + refresh_session_pub, + &refresh_session); + if (GNUNET_OK != res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + + GNUNET_assert (0 != refresh_session.reveal_ok); + + root = json_object (); + list = json_array (); + json_object_set_new (root, "ev_sigs", list); + + for (newcoin_index = 0; newcoin_index < refresh_session.num_newcoins; newcoin_index++) + { + struct TALER_RSA_Signature ev_sig; + + res = TALER_MINT_DB_get_refresh_collectable (db_conn, + newcoin_index, + refresh_session_pub, + &ev_sig); + if (GNUNET_OK != res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + json_array_append_new (list, + TALER_JSON_from_data (&ev_sig, + sizeof (struct TALER_RSA_Signature))); + } + return send_response_json (connection, + root, + MHD_HTTP_OK); +} + + +/** + * Handle a "/refresh/reveal" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_reveal (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub; + int res; + PGconn *db_conn; + struct RefreshSession refresh_session; + struct MintKeyState *key_state; + int i; + int j; + json_t *root; + + res = process_post_json (connection, + connection_cls, + upload_data, upload_data_size, + &root); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + return MHD_YES; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "session_pub", + JNAV_RET_DATA, + &refresh_session_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + return res; + } + + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + { + GNUNET_break (0); + return MHD_NO; + } + + /* Send response immediately if we already know the session, + * and the session commited already. + * Do _not_ care about fields other than session_pub in this case. */ + + res = TALER_MINT_DB_get_refresh_session (db_conn, &refresh_session_pub, &refresh_session); + if (GNUNET_YES == res && 0 != refresh_session.reveal_ok) + return helper_refresh_reveal_send_response (connection, db_conn, &refresh_session_pub); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return MHD_NO; + } + + /* Check that the transfer private keys match their commitments. + * Then derive the shared secret for each kappa, and check that they match. */ + + for (i = 0; i < refresh_session.kappa; i++) + { + struct GNUNET_HashCode last_shared_secret; + int secret_initialized = GNUNET_NO; + + if (i == (refresh_session.noreveal_index % refresh_session.kappa)) + continue; + + for (j = 0; j < refresh_session.num_oldcoins; j++) + { + struct GNUNET_CRYPTO_EcdsaPrivateKey transfer_priv; + struct RefreshCommitLink commit_link; + struct GNUNET_CRYPTO_EcdsaPublicKey coin_pub; + struct GNUNET_HashCode transfer_secret; + struct GNUNET_HashCode shared_secret; + + res = request_json_require_nav (connection, root, + JNAV_FIELD, "transfer_privs", + JNAV_INDEX, (int) i, + JNAV_INDEX, (int) j, + JNAV_RET_DATA, + &transfer_priv, + sizeof (struct GNUNET_CRYPTO_EddsaPrivateKey)); + + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + return res; + } + + res = TALER_MINT_DB_get_refresh_commit_link (db_conn, + &refresh_session_pub, + i, j, + &commit_link); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + res = TALER_MINT_DB_get_refresh_melt (db_conn, &refresh_session_pub, j, &coin_pub); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + /* We're converting key types here, which is not very nice + * but necessary and harmless (keys will be thrown away later). */ + if (GNUNET_OK != GNUNET_CRYPTO_ecc_ecdh ((struct GNUNET_CRYPTO_EcdhePrivateKey *) &transfer_priv, + (struct GNUNET_CRYPTO_EcdhePublicKey *) &coin_pub, + &transfer_secret)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (0 >= TALER_refresh_decrypt (commit_link.shared_secret_enc, TALER_REFRESH_SHARED_SECRET_LENGTH, + &transfer_secret, &shared_secret)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "decryption failed\n"); + return GNUNET_SYSERR; + } + + if (GNUNET_NO == secret_initialized) + { + secret_initialized = GNUNET_YES; + last_shared_secret = shared_secret; + } + else if (0 != memcmp (&shared_secret, &last_shared_secret, sizeof (struct GNUNET_HashCode))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "shared secrets do not match\n"); + return GNUNET_SYSERR; + } + + { + struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub_check; + GNUNET_CRYPTO_ecdsa_key_get_public (&transfer_priv, &transfer_pub_check); + if (0 != memcmp (&transfer_pub_check, &commit_link.transfer_pub, sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "transfer keys do not match\n"); + return GNUNET_SYSERR; + } + } + } + + /* Check that the commitments for all new coins were correct */ + + for (j = 0; j < refresh_session.num_newcoins; j++) + { + struct RefreshCommitCoin commit_coin; + struct LinkData link_data; + struct TALER_RSA_BlindedSignaturePurpose *coin_ev_check; + struct GNUNET_CRYPTO_EcdsaPublicKey coin_pub; + struct TALER_RSA_BlindingKey *bkey; + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + + bkey = NULL; + res = TALER_MINT_DB_get_refresh_commit_coin (db_conn, + &refresh_session_pub, + i, j, + &commit_coin); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + + if (0 >= TALER_refresh_decrypt (commit_coin.link_enc, sizeof (struct LinkData), + &last_shared_secret, &link_data)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "decryption failed\n"); + return GNUNET_SYSERR; + } + + GNUNET_CRYPTO_ecdsa_key_get_public (&link_data.coin_priv, &coin_pub); + if (NULL == (bkey = TALER_RSA_blinding_key_decode (&link_data.bkey_enc))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Invalid blinding key\n"); + return GNUNET_SYSERR; + } + res = TALER_MINT_DB_get_refresh_order (db_conn, j, &refresh_session_pub, &denom_pub); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (NULL == (coin_ev_check = + TALER_RSA_message_blind (&coin_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), + bkey, + &denom_pub))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "blind failed\n"); + return GNUNET_SYSERR; + } + + if (0 != memcmp (&coin_ev_check, + &commit_coin.coin_ev, + sizeof (struct TALER_RSA_BlindedSignaturePurpose))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "blind envelope does not match for kappa=%d, old=%d\n", + (int) i, (int) j); + return GNUNET_SYSERR; + } + } + } + + + if (GNUNET_OK != TALER_MINT_DB_transaction (db_conn)) + { + GNUNET_break (0); + return MHD_NO; + } + + for (j = 0; j < refresh_session.num_newcoins; j++) + { + struct RefreshCommitCoin commit_coin; + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + struct TALER_MINT_DenomKeyIssue *dki; + struct TALER_RSA_Signature ev_sig; + + res = TALER_MINT_DB_get_refresh_commit_coin (db_conn, + &refresh_session_pub, + refresh_session.noreveal_index % refresh_session.kappa, + j, + &commit_coin); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + res = TALER_MINT_DB_get_refresh_order (db_conn, j, &refresh_session_pub, &denom_pub); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + + key_state = TALER_MINT_key_state_acquire (); + dki = TALER_MINT_get_denom_key (key_state, &denom_pub); + TALER_MINT_key_state_release (key_state); + if (NULL == dki) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_RSA_sign (dki->denom_priv, + &commit_coin.coin_ev, + sizeof (struct TALER_RSA_BlindedSignaturePurpose), + &ev_sig)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + res = TALER_MINT_DB_insert_refresh_collectable (db_conn, + j, + &refresh_session_pub, + &ev_sig); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + /* mark that reveal was successful */ + + res = TALER_MINT_DB_set_reveal_ok (db_conn, &refresh_session_pub); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != TALER_MINT_DB_commit (db_conn)) + { + GNUNET_break (0); + return MHD_NO; + } + + return helper_refresh_reveal_send_response (connection, db_conn, &refresh_session_pub); +} + + +/** + * Handle a "/refresh/link" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_link (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct GNUNET_CRYPTO_EcdsaPublicKey coin_pub; + int res; + json_t *root; + json_t *list; + PGconn *db_conn; + struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub; + struct SharedSecretEnc shared_secret_enc; + + res = TALER_MINT_mhd_request_arg_data (connection, + "coin_pub", + &coin_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_OK != res) + return MHD_YES; + + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + list = json_array (); + root = json_object (); + json_object_set_new (root, "new_coins", list); + + res = TALER_db_get_transfer (db_conn, + &coin_pub, + &transfer_pub, + &shared_secret_enc); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + { + return request_send_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "link data not found (transfer)"); + } + GNUNET_assert (GNUNET_OK == res); + + res = TALER_db_get_link (db_conn, &coin_pub, link_iter, list); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + { + return request_send_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "link data not found (link)"); + } + GNUNET_assert (GNUNET_OK == res); + json_object_set_new (root, "transfer_pub", + TALER_JSON_from_data (&transfer_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))); + json_object_set_new (root, "secret_enc", + TALER_JSON_from_data (&shared_secret_enc, + sizeof (struct SharedSecretEnc))); + return send_response_json (connection, root, MHD_HTTP_OK); +} + + +/* end of taler-mint-httpd_refresh.c */ diff --git a/src/mint/taler-mint-httpd_refresh.h b/src/mint/taler-mint-httpd_refresh.h new file mode 100644 index 000000000..20e7d97c2 --- /dev/null +++ b/src/mint/taler-mint-httpd_refresh.h @@ -0,0 +1,103 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_refresh.h + * @brief Handle /refresh/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_REFRESH_H +#define TALER_MINT_HTTPD_REFRESH_H + +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + + +/** + * Handle a "/refresh/melt" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_melt (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Handle a "/refresh/commit" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_commit (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Handle a "/refresh/link" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_link (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Handle a "/refresh/reveal" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_refresh_reveal (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +#endif diff --git a/src/mint/taler-mint-httpd_withdraw.c b/src/mint/taler-mint-httpd_withdraw.c new file mode 100644 index 000000000..7ffa45706 --- /dev/null +++ b/src/mint/taler-mint-httpd_withdraw.c @@ -0,0 +1,400 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_withdraw.c + * @brief Handle /withdraw/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <libpq-fe.h> +#include <pthread.h> +#include "mint.h" +#include "mint_db.h" +#include "taler_types.h" +#include "taler_signatures.h" +#include "taler_rsa.h" +#include "taler_json_lib.h" +#include "taler_microhttpd_lib.h" +#include "taler-mint-httpd_keys.h" +#include "taler-mint-httpd_mhd.h" +#include "taler-mint-httpd_withdraw.h" + + +/** + * Convert a signature (with purpose) to + * a JSON object representation. + * + * @param purpose purpose of the signature + * @param signature the signature + * @return the JSON reporesentation of the signature with purpose + */ +static json_t * +sig_to_json (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose, + const struct GNUNET_CRYPTO_EddsaSignature *signature) +{ + json_t *root; + json_t *el; + + root = json_object (); + + el = json_integer ((json_int_t) ntohl (purpose->size)); + json_object_set_new (root, "size", el); + + el = json_integer ((json_int_t) ntohl (purpose->purpose)); + json_object_set_new (root, "purpose", el); + + el = TALER_JSON_from_data (signature, sizeof (struct GNUNET_CRYPTO_EddsaSignature)); + json_object_set_new (root, "sig", el); + + return root; +} + + +/** + * Sign a reserve's status with the current signing key. + * + * @param reserve the reserve to sign + * @param key_state the key state containing the current + * signing private key + */ +static void +sign_reserve (struct Reserve *reserve, + struct MintKeyState *key_state) +{ + reserve->status_sign_pub = key_state->current_sign_key_issue.signkey_pub; + reserve->status_sig_purpose.purpose = htonl (TALER_SIGNATURE_RESERVE_STATUS); + reserve->status_sig_purpose.size = htonl (sizeof (struct Reserve) - + offsetof (struct Reserve, status_sig_purpose)); + GNUNET_CRYPTO_eddsa_sign (&key_state->current_sign_key_issue.signkey_priv, + &reserve->status_sig_purpose, + &reserve->status_sig); +} + + +/** + * Handle a "/withdraw/status" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_withdraw_status (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; + PGconn *db_conn; + int res; + struct Reserve reserve; + struct MintKeyState *key_state; + int must_update = GNUNET_NO; + json_t *json; + + res = TALER_MINT_mhd_request_arg_data (connection, + "reserve_pub", + &reserve_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_OK != res) + return MHD_YES; + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + res = TALER_MINT_DB_get_reserve (db_conn, + &reserve_pub, + &reserve); + if (GNUNET_SYSERR == res) + return TALER_MINT_helper_send_json_pack (rh, + connection, + connection_cls, + 0 /* no caching */, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", + "Reserve not found"); + if (GNUNET_OK != res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + key_state = TALER_MINT_key_state_acquire (); + if (0 != memcmp (&key_state->current_sign_key_issue.signkey_pub, + &reserve.status_sign_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) + { + sign_reserve (&reserve, key_state); + must_update = GNUNET_YES; + } + if ((GNUNET_YES == must_update) && + (GNUNET_OK != TALER_MINT_DB_update_reserve (db_conn, &reserve, !must_update))) + { + GNUNET_break (0); + return MHD_YES; + } + + /* Convert the public information of a reserve (i.e. + excluding private key) to a JSON object. */ + json = json_object (); + json_object_set_new (json, + "balance", + TALER_JSON_from_amount (TALER_amount_ntoh (reserve.balance))); + json_object_set_new (json, + "expiration", + TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (reserve.expiration))); + json_object_set_new (json, + "signature", + sig_to_json (&reserve.status_sig_purpose, + &reserve.status_sig)); + + return send_response_json (connection, + json, + MHD_HTTP_OK); +} + + +/** + * Send positive, normal response for "/withdraw/sign". + * + * @param connection the connection to send the response to + * @param collectable the collectable blindcoin (i.e. the blindly signed coin) + * @return a MHD result code + */ +static int +helper_withdraw_sign_send_reply (struct MHD_Connection *connection, + const struct CollectableBlindcoin *collectable) +{ + json_t *root = json_object (); + + json_object_set_new (root, "ev_sig", + TALER_JSON_from_data (&collectable->ev_sig, + sizeof (struct TALER_RSA_Signature))); + return send_response_json (connection, + root, + MHD_HTTP_OK); +} + + +/** + * Handle a "/withdraw/sign" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_withdraw_sign (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct TALER_WithdrawRequest wsrd; + int res; + PGconn *db_conn; + struct Reserve reserve; + struct MintKeyState *key_state; + struct CollectableBlindcoin collectable; + struct TALER_MINT_DenomKeyIssue *dki; + struct TALER_RSA_Signature ev_sig; + struct TALER_Amount amount_required; + + memset (&wsrd, + 0, + sizeof (struct TALER_WithdrawRequest)); + res = TALER_MINT_mhd_request_arg_data (connection, + "reserve_pub", + &wsrd.reserve_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_OK != res) + return MHD_YES; + res = TALER_MINT_mhd_request_arg_data (connection, + "denom_pub", + &wsrd.denomination_pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_OK != res) + return MHD_YES; + res = TALER_MINT_mhd_request_arg_data (connection, + "coin_ev", + &wsrd.coin_envelope, + sizeof (struct TALER_RSA_Signature)); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_OK != res) + return MHD_YES; + res = TALER_MINT_mhd_request_arg_data (connection, + "reserve_sig", + &wsrd.sig, + sizeof (struct GNUNET_CRYPTO_EddsaSignature)); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_OK != res) + return MHD_YES; + + if (NULL == (db_conn = TALER_MINT_DB_get_connection ())) + { + // FIXME: return 'internal error'? + GNUNET_break (0); + return MHD_NO; + } + + res = TALER_MINT_DB_get_collectable_blindcoin (db_conn, + &wsrd.coin_envelope, + &collectable); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + + /* Don't sign again if we have already signed the coin */ + if (GNUNET_YES == res) + return helper_withdraw_sign_send_reply (connection, + &collectable); + GNUNET_assert (GNUNET_NO == res); + res = TALER_MINT_DB_get_reserve (db_conn, + &wsrd.reserve_pub, + &reserve); + if (GNUNET_SYSERR == res) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_NO == res) + return request_send_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "Reserve not found"); + + // fill out all the missing info in the request before + // we can check the signature on the request + + wsrd.purpose.purpose = htonl (TALER_SIGNATURE_WITHDRAW); + wsrd.purpose.size = htonl (sizeof (struct TALER_WithdrawRequest) - + offsetof (struct TALER_WithdrawRequest, purpose)); + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WITHDRAW, + &wsrd.purpose, + &wsrd.sig, + &wsrd.reserve_pub)) + return request_send_json_pack (connection, + MHD_HTTP_UNAUTHORIZED, + "{s:s}", + "error", "Invalid Signature"); + + key_state = TALER_MINT_key_state_acquire (); + dki = TALER_MINT_get_denom_key (key_state, + &wsrd.denomination_pub); + TALER_MINT_key_state_release (key_state); + if (NULL == dki) + return request_send_json_pack (connection, MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", "Denomination not found"); + + amount_required = TALER_amount_ntoh (dki->value); + amount_required = TALER_amount_add (amount_required, + TALER_amount_ntoh (dki->fee_withdraw)); + + if (0 < TALER_amount_cmp (amount_required, + TALER_amount_ntoh (reserve.balance))) + return request_send_json_pack (connection, + MHD_HTTP_PAYMENT_REQUIRED, + "{s:s}", + "error", "Insufficient funds"); + if (GNUNET_OK != TALER_RSA_sign (dki->denom_priv, + &wsrd.coin_envelope, + sizeof (struct TALER_RSA_BlindedSignaturePurpose), + &ev_sig)) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + + reserve.balance = TALER_amount_hton (TALER_amount_subtract (TALER_amount_ntoh (reserve.balance), + amount_required)); + if (GNUNET_OK != + TALER_MINT_DB_update_reserve (db_conn, + &reserve, + GNUNET_YES)) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return MHD_NO; + } + + collectable.ev = wsrd.coin_envelope; + collectable.ev_sig = ev_sig; + collectable.reserve_pub = wsrd.reserve_pub; + collectable.reserve_sig = wsrd.sig; + if (GNUNET_OK != + TALER_MINT_DB_insert_collectable_blindcoin (db_conn, + &collectable)) + { + // FIXME: return 'internal error' + GNUNET_break (0); + return GNUNET_NO;; + } + return helper_withdraw_sign_send_reply (connection, + &collectable); +} + +/* end of taler-mint-httpd_withdraw.c */ diff --git a/src/mint/taler-mint-httpd_withdraw.h b/src/mint/taler-mint-httpd_withdraw.h new file mode 100644 index 000000000..1d292ebd9 --- /dev/null +++ b/src/mint/taler-mint-httpd_withdraw.h @@ -0,0 +1,65 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e.V. + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_withdraw.h + * @brief Handle /withdraw/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_WITHDRAW_H +#define TALER_MINT_HTTPD_WITHDRAW_H + +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + +/** + * Handle a "/withdraw/status" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_withdraw_status (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Handle a "/withdraw/sign" request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[IN|OUT] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TALER_MINT_handler_withdraw_sign (struct RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif diff --git a/src/mint/taler-mint-keycheck.c b/src/mint/taler-mint-keycheck.c new file mode 100644 index 000000000..c6186859c --- /dev/null +++ b/src/mint/taler-mint-keycheck.c @@ -0,0 +1,169 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-keycheck.c + * @brief Check mint keys for validity. + * @author Florian Dold + * @author Benedikt Mueller + */ + +#include <platform.h> +#include <gnunet/gnunet_util_lib.h> +#include "mint.h" +#include "taler_signatures.h" + + +static char *mintdir; +static struct GNUNET_CONFIGURATION_Handle *kcfg; + + +static int +signkeys_iter (void *cls, const struct TALER_MINT_SignKeyIssue *ski) +{ + struct GNUNET_TIME_Absolute start; + + printf ("iterating over key for start time %s\n", + GNUNET_STRINGS_absolute_time_to_string (GNUNET_TIME_absolute_ntoh (ski->start))); + + start = GNUNET_TIME_absolute_ntoh (ski->start); + + if (ntohl (ski->purpose.size) != + (sizeof (struct TALER_MINT_SignKeyIssue) - offsetof (struct TALER_MINT_SignKeyIssue, purpose))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Signkey with start %s has invalid purpose field (timestamp: %llu)\n", + GNUNET_STRINGS_absolute_time_to_string (start), + (long long) start.abs_value_us); + return GNUNET_SYSERR; + } + + + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNKEY, + &ski->purpose, + &ski->signature, + &ski->master_pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Signkey with start %s has invalid signature (timestamp: %llu)\n", + GNUNET_STRINGS_absolute_time_to_string (start), + (long long) start.abs_value_us); + return GNUNET_SYSERR; + } + printf ("key valid\n"); + return GNUNET_OK; +} + + +static int +mint_signkeys_check () +{ + if (0 > TALER_MINT_signkeys_iterate (mintdir, signkeys_iter, NULL)) + return GNUNET_NO; + return GNUNET_OK; +} + + +static int denomkeys_iter (void *cls, + const char *alias, + const struct TALER_MINT_DenomKeyIssue *dki) +{ + struct GNUNET_TIME_Absolute start; + + start = GNUNET_TIME_absolute_ntoh (dki->start); + + if (ntohl (dki->purpose.size) != + (sizeof (struct TALER_MINT_DenomKeyIssue) - offsetof (struct TALER_MINT_DenomKeyIssue, purpose))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Denomkey for '%s' with start %s has invalid purpose field (timestamp: %llu)\n", + alias, + GNUNET_STRINGS_absolute_time_to_string (start), + (long long) start.abs_value_us); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOM, + &dki->purpose, + &dki->signature, + &dki->master)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Denomkey for '%s'with start %s has invalid signature (timestamp: %llu)\n", + alias, + GNUNET_STRINGS_absolute_time_to_string (start), + (long long) start.abs_value_us); + return GNUNET_SYSERR; + } + printf ("denom key valid\n"); + + return GNUNET_OK; +} + + +static int +mint_denomkeys_check () +{ + if (0 > TALER_MINT_denomkeys_iterate (mintdir, denomkeys_iter, NULL)) + return GNUNET_NO; + return GNUNET_OK; +} + + +static int +mint_keys_check (void) +{ + if (GNUNET_OK != mint_signkeys_check ()) + return GNUNET_NO; + return mint_denomkeys_check (); +} + + +/** + * The main function of the keyup tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"), + {'d', "mint-dir", "DIR", + "mint directory with keys to update", 1, + &GNUNET_GETOPT_set_filename, &mintdir}, + GNUNET_GETOPT_OPTION_END + }; + + GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("taler-mint-keycheck", "WARNING", NULL)); + + if (GNUNET_GETOPT_run ("taler-mint-keyup", options, argc, argv) < 0) + return 1; + if (NULL == mintdir) + { + fprintf (stderr, "mint directory not given\n"); + return 1; + } + + kcfg = TALER_MINT_config_load (mintdir); + if (NULL == kcfg) + { + fprintf (stderr, "can't load mint configuration\n"); + return 1; + } + if (GNUNET_OK != mint_keys_check ()) + return 1; + return 0; +} + diff --git a/src/mint/taler-mint-keyup.c b/src/mint/taler-mint-keyup.c new file mode 100644 index 000000000..8a1a77882 --- /dev/null +++ b/src/mint/taler-mint-keyup.c @@ -0,0 +1,657 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-keyup.c + * @brief Update the mint's keys for coins and signatures, + * using the mint's offline master key. + * @author Florian Dold + * @author Benedikt Mueller + */ + +#include <platform.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#include "taler_signatures.h" +#include "mint.h" + +#define HASH_CUTOFF 20 + +/** + * Macro to round microseconds to seconds in GNUNET_TIME_* structs. + */ +#define ROUND_TO_SECS(name,us_field) name.us_field -= name.us_field % (1000 * 1000); + + +GNUNET_NETWORK_STRUCT_BEGIN + +struct CoinTypeNBO +{ + struct GNUNET_TIME_RelativeNBO duration_spend; + struct GNUNET_TIME_RelativeNBO duration_withdraw; + struct TALER_AmountNBO value; + struct TALER_AmountNBO fee_withdraw; + struct TALER_AmountNBO fee_deposit; + struct TALER_AmountNBO fee_refresh; +}; + +GNUNET_NETWORK_STRUCT_END + +struct CoinTypeParams +{ + struct GNUNET_TIME_Relative duration_spend; + struct GNUNET_TIME_Relative duration_withdraw; + struct GNUNET_TIME_Relative duration_overlap; + struct TALER_Amount value; + struct TALER_Amount fee_withdraw; + struct TALER_Amount fee_deposit; + struct TALER_Amount fee_refresh; + struct GNUNET_TIME_Absolute anchor; +}; + + +/** + * Filename of the master private key. + */ +static char *masterkeyfile; + +/** + * Director of the mint, containing the keys. + */ +static char *mintdir; + +/** + * Time to pretend when the key update is executed. + */ +static char *pretend_time_str; + +/** + * Handle to the mint's configuration + */ +static struct GNUNET_CONFIGURATION_Handle *kcfg; + +/** + * Time when the key update is executed. Either the actual current time, or a + * pretended time. + */ +static struct GNUNET_TIME_Absolute now; + +/** + * Master private key of the mint. + */ +static struct GNUNET_CRYPTO_EddsaPrivateKey *master_priv; + +/** + * Master public key of the mint. + */ +static struct GNUNET_CRYPTO_EddsaPublicKey *master_pub; + +/** + * Until what time do we provide keys? + */ +static struct GNUNET_TIME_Absolute lookahead_sign_stamp; + + +int +config_get_denom (const char *section, const char *option, struct TALER_Amount *denom) +{ + char *str; + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg, section, option, &str)) + return GNUNET_NO; + if (GNUNET_OK != TALER_string_to_amount (str, denom)) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +char * +get_signkey_dir () +{ + char *dir; + size_t len; + len = GNUNET_asprintf (&dir, ("%s" DIR_SEPARATOR_STR DIR_SIGNKEYS), mintdir); + GNUNET_assert (len > 0); + return dir; +} + + +char * +get_signkey_file (struct GNUNET_TIME_Absolute start) +{ + char *dir; + size_t len; + len = GNUNET_asprintf (&dir, ("%s" DIR_SEPARATOR_STR DIR_SIGNKEYS DIR_SEPARATOR_STR "%llu"), + mintdir, (long long) start.abs_value_us); + GNUNET_assert (len > 0); + return dir; +} + + + +/** + * Hash the data defining the coin type. + * Exclude information that may not be the same for all + * instances of the coin type (i.e. the anchor, overlap). + */ +void +hash_coin_type (const struct CoinTypeParams *p, struct GNUNET_HashCode *hash) +{ + struct CoinTypeNBO p_nbo; + + memset (&p_nbo, 0, sizeof (struct CoinTypeNBO)); + + p_nbo.duration_spend = GNUNET_TIME_relative_hton (p->duration_spend); + p_nbo.duration_withdraw = GNUNET_TIME_relative_hton (p->duration_withdraw); + p_nbo.value = TALER_amount_hton (p->value); + p_nbo.fee_withdraw = TALER_amount_hton (p->fee_withdraw); + p_nbo.fee_deposit = TALER_amount_hton (p->fee_deposit); + p_nbo.fee_refresh = TALER_amount_hton (p->fee_refresh); + + GNUNET_CRYPTO_hash (&p_nbo, sizeof (struct CoinTypeNBO), hash); +} + + +static const char * +get_cointype_dir (const struct CoinTypeParams *p) +{ + static char dir[4096]; + size_t len; + struct GNUNET_HashCode hash; + char *hash_str; + char *val_str; + unsigned int i; + + hash_coin_type (p, &hash); + hash_str = TALER_data_to_string_alloc (&hash, sizeof (struct GNUNET_HashCode)); + GNUNET_assert (HASH_CUTOFF <= strlen (hash_str) + 1); + GNUNET_assert (NULL != hash_str); + hash_str[HASH_CUTOFF] = 0; + + val_str = TALER_amount_to_string (p->value); + for (i = 0; i < strlen (val_str); i++) + if (':' == val_str[i] || '.' == val_str[i]) + val_str[i] = '_'; + + len = GNUNET_snprintf (dir, sizeof (dir), + ("%s" DIR_SEPARATOR_STR DIR_DENOMKEYS DIR_SEPARATOR_STR "%s-%s"), + mintdir, val_str, hash_str); + GNUNET_assert (len > 0); + GNUNET_free (hash_str); + return dir; +} + + +static const char * +get_cointype_file (struct CoinTypeParams *p, + struct GNUNET_TIME_Absolute start) +{ + const char *dir; + static char filename[4096]; + size_t len; + dir = get_cointype_dir (p); + len = GNUNET_snprintf (filename, sizeof (filename), ("%s" DIR_SEPARATOR_STR "%llu"), + dir, (unsigned long long) start.abs_value_us); + GNUNET_assert (len > 0); + return filename; +} + + +/** + * Get the latest key file from the past. + * + * @param cls closure + * @param filename complete filename (absolute path) + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +static int +get_anchor_iter (void *cls, + const char *filename) +{ + struct GNUNET_TIME_Absolute stamp; + struct GNUNET_TIME_Absolute *anchor = cls; + const char *base; + char *end = NULL; + + base = GNUNET_STRINGS_get_short_name (filename); + stamp.abs_value_us = strtol (base, &end, 10); + + if ((NULL == end) || (0 != *end)) + { + fprintf(stderr, "Ignoring unexpected file '%s'.\n", filename); + return GNUNET_OK; + } + + // TODO: check if it's actually a valid key file + + if ((stamp.abs_value_us <= now.abs_value_us) && (stamp.abs_value_us > anchor->abs_value_us)) + *anchor = stamp; + + return GNUNET_OK; +} + + +/** + * Get the timestamp where the first new key should be generated. + * Relies on correctly named key files. + * + * @param dir directory with the signed stuff + * @param duration how long is one key valid? + * @param overlap what's the overlap between the keys validity period? + * @param[out] anchor the timestamp where the first new key should be generated + */ +void +get_anchor (const char *dir, + struct GNUNET_TIME_Relative duration, + struct GNUNET_TIME_Relative overlap, + struct GNUNET_TIME_Absolute *anchor) +{ + GNUNET_assert (0 == duration.rel_value_us % 1000000); + GNUNET_assert (0 == overlap.rel_value_us % 1000000); + if (GNUNET_YES != GNUNET_DISK_directory_test (dir, GNUNET_YES)) + { + *anchor = now; + printf ("Can't look for anchor (%s)\n", dir); + return; + } + + *anchor = GNUNET_TIME_UNIT_ZERO_ABS; + if (-1 == GNUNET_DISK_directory_scan (dir, &get_anchor_iter, anchor)) + { + *anchor = now; + return; + } + + if ((GNUNET_TIME_absolute_add (*anchor, duration)).abs_value_us < now.abs_value_us) + { + // there's no good anchor, start from now + // (existing keys are too old) + *anchor = now; + } + else if (anchor->abs_value_us != now.abs_value_us) + { + // we have a good anchor + *anchor = GNUNET_TIME_absolute_add (*anchor, duration); + *anchor = GNUNET_TIME_absolute_subtract (*anchor, overlap); + } + // anchor is now the stamp where we need to create a new key +} + +static void +create_signkey_issue (struct GNUNET_TIME_Absolute start, + struct GNUNET_TIME_Relative duration, + struct TALER_MINT_SignKeyIssue *issue) +{ + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + + priv = GNUNET_CRYPTO_eddsa_key_create (); + GNUNET_assert (NULL != priv); + issue->signkey_priv = *priv; + GNUNET_free (priv); + issue->master_pub = *master_pub; + issue->start = GNUNET_TIME_absolute_hton (start); + issue->expire = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (start, duration)); + + GNUNET_CRYPTO_eddsa_key_get_public (&issue->signkey_priv, &issue->signkey_pub); + + issue->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNKEY); + issue->purpose.size = htonl (sizeof (struct TALER_MINT_SignKeyIssue) - offsetof (struct TALER_MINT_SignKeyIssue, purpose)); + + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_sign (master_priv, &issue->purpose, &issue->signature)) + { + GNUNET_abort (); + } +} + + +static int +check_signkey_valid (const char *signkey_filename) +{ + // FIXME: do real checks + return GNUNET_OK; +} + + +int +mint_keys_update_signkeys () +{ + struct GNUNET_TIME_Relative signkey_duration; + struct GNUNET_TIME_Absolute anchor; + char *signkey_dir; + + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_keys", "signkey_duration", &signkey_duration)) + { + fprintf (stderr, "Can't read config value mint_keys.signkey_duration\n"); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (signkey_duration, rel_value_us); + signkey_dir = get_signkey_dir (); + // make sure the directory exists + if (GNUNET_OK != GNUNET_DISK_directory_create (signkey_dir)) + { + fprintf (stderr, "Cant create signkey dir\n"); + return GNUNET_SYSERR; + } + + get_anchor (signkey_dir, signkey_duration, GNUNET_TIME_UNIT_ZERO, &anchor); + + while (anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) { + char *skf; + skf = get_signkey_file (anchor); + if (GNUNET_YES != GNUNET_DISK_file_test (skf)) + { + struct TALER_MINT_SignKeyIssue signkey_issue; + ssize_t nwrite; + printf ("Generating signing key for %s.\n", GNUNET_STRINGS_absolute_time_to_string (anchor)); + create_signkey_issue (anchor, signkey_duration, &signkey_issue); + nwrite = GNUNET_DISK_fn_write (skf, &signkey_issue, sizeof (struct TALER_MINT_SignKeyIssue), + (GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_USER_READ)); + if (nwrite != sizeof (struct TALER_MINT_SignKeyIssue)) + { + fprintf (stderr, "Can't write to file '%s'\n", skf); + return GNUNET_SYSERR; + } + } + else if (GNUNET_OK != check_signkey_valid (skf)) + { + return GNUNET_SYSERR; + } + anchor = GNUNET_TIME_absolute_add (anchor, signkey_duration); + } + return GNUNET_OK; +} + + +int +get_cointype_params (const char *ct, struct CoinTypeParams *params) +{ + const char *dir; + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_denom_duration_withdraw", ct, ¶ms->duration_withdraw)) + { + fprintf (stderr, "Withdraw duration not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (params->duration_withdraw, rel_value_us); + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_denom_duration_spend", ct, ¶ms->duration_spend)) + { + fprintf (stderr, "Spend duration not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (params->duration_spend, rel_value_us); + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_denom_duration_overlap", ct, ¶ms->duration_overlap)) + { + fprintf (stderr, "Overlap duration not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (params->duration_overlap, rel_value_us); + + if (GNUNET_OK != config_get_denom ("mint_denom_value", ct, ¶ms->value)) + { + fprintf (stderr, "Value not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != config_get_denom ("mint_denom_fee_withdraw", ct, ¶ms->fee_withdraw)) + { + fprintf (stderr, "Withdraw fee not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != config_get_denom ("mint_denom_fee_deposit", ct, ¶ms->fee_deposit)) + { + fprintf (stderr, "Deposit fee not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != config_get_denom ("mint_denom_fee_refresh", ct, ¶ms->fee_refresh)) + { + fprintf (stderr, "Deposit fee not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + + dir = get_cointype_dir (params); + get_anchor (dir, params->duration_spend, params->duration_overlap, ¶ms->anchor); + return GNUNET_OK; +} + + +static void +create_denomkey_issue (struct CoinTypeParams *params, struct TALER_MINT_DenomKeyIssue *dki) +{ + GNUNET_assert (NULL != (dki->denom_priv = TALER_RSA_key_create ())); + TALER_RSA_key_get_public (dki->denom_priv, &dki->denom_pub); + dki->master = *master_pub; + dki->start = GNUNET_TIME_absolute_hton (params->anchor); + dki->expire_withdraw = + GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, + params->duration_withdraw)); + dki->expire_spend = + GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, + params->duration_spend)); + dki->value = TALER_amount_hton (params->value); + dki->fee_withdraw = TALER_amount_hton (params->fee_withdraw); + dki->fee_deposit = TALER_amount_hton (params->fee_deposit); + dki->fee_refresh = TALER_amount_hton (params->fee_refresh); + + dki->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOM); + dki->purpose.size = htonl (sizeof (struct TALER_MINT_DenomKeyIssue) - offsetof (struct TALER_MINT_DenomKeyIssue, purpose)); + + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_sign (master_priv, &dki->purpose, &dki->signature)) + { + GNUNET_abort (); + } +} + + +static int +check_cointype_valid (const char *filename, struct CoinTypeParams *params) +{ + // FIXME: add real checks + return GNUNET_OK; +} + + +int +mint_keys_update_cointype (const char *coin_alias) +{ + struct CoinTypeParams p; + const char *cointype_dir; + + if (GNUNET_OK != get_cointype_params (coin_alias, &p)) + return GNUNET_SYSERR; + + cointype_dir = get_cointype_dir (&p); + if (GNUNET_OK != GNUNET_DISK_directory_create (cointype_dir)) + return GNUNET_SYSERR; + + while (p.anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) { + const char *dkf; + dkf = get_cointype_file (&p, p.anchor); + + if (GNUNET_YES != GNUNET_DISK_file_test (dkf)) + { + struct TALER_MINT_DenomKeyIssue denomkey_issue; + int ret; + printf ("Generating denomination key for type '%s', start %s.\n", + coin_alias, GNUNET_STRINGS_absolute_time_to_string (p.anchor)); + printf ("Target path: %s\n", dkf); + create_denomkey_issue (&p, &denomkey_issue); + ret = TALER_MINT_write_denom_key (dkf, &denomkey_issue); + TALER_RSA_key_free (denomkey_issue.denom_priv); + if (GNUNET_OK != ret) + { + fprintf (stderr, "Can't write to file '%s'\n", dkf); + return GNUNET_SYSERR; + } + } + else if (GNUNET_OK != check_cointype_valid (dkf, &p)) + { + return GNUNET_SYSERR; + } + p.anchor = GNUNET_TIME_absolute_add (p.anchor, p.duration_spend); + p.anchor = GNUNET_TIME_absolute_subtract (p.anchor, p.duration_overlap); + } + return GNUNET_OK; +} + + +int +mint_keys_update_denomkeys () +{ + char *coin_types; + char *ct; + char *tok_ctx; + + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg, "mint_keys", "coin_types", &coin_types)) + { + fprintf (stderr, "mint_keys.coin_types not in configuration\n"); + return GNUNET_SYSERR; + } + + for (ct = strtok_r (coin_types, " ", &tok_ctx); + ct != NULL; + ct = strtok_r (NULL, " ", &tok_ctx)) + { + if (GNUNET_OK != mint_keys_update_cointype (ct)) + { + GNUNET_free (coin_types); + return GNUNET_SYSERR; + } + } + GNUNET_free (coin_types); + return GNUNET_OK; +} + + +static int +mint_keys_update () +{ + int ret; + struct GNUNET_TIME_Relative lookahead_sign; + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_keys", "lookahead_sign", &lookahead_sign)) + { + fprintf (stderr, "mint_keys.lookahead_sign not found\n"); + return GNUNET_SYSERR; + } + if (lookahead_sign.rel_value_us == 0) + { + fprintf (stderr, "mint_keys.lookahead_sign must not be zero\n"); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (lookahead_sign, rel_value_us); + lookahead_sign_stamp = GNUNET_TIME_absolute_add (now, lookahead_sign); + + ret = mint_keys_update_signkeys (); + if (GNUNET_OK != ret) + return GNUNET_SYSERR; + + return mint_keys_update_denomkeys (); +} + + +/** + * The main function of the keyup tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"), + {'m', "master-key", "FILE", + "master key file (private key)", 1, + &GNUNET_GETOPT_set_filename, &masterkeyfile}, + {'d', "mint-dir", "DIR", + "mint directory with keys to update", 1, + &GNUNET_GETOPT_set_filename, &mintdir}, + {'t', "time", "TIMESTAMP", + "pretend it is a different time for the update", 0, + &GNUNET_GETOPT_set_string, &pretend_time_str}, + GNUNET_GETOPT_OPTION_END + }; + + GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("taler-mint-keyup", "WARNING", NULL)); + + if (GNUNET_GETOPT_run ("taler-mint-keyup", options, argc, argv) < 0) + return 1; + if (NULL == mintdir) + { + fprintf (stderr, "mint directory not given\n"); + return 1; + } + + if (NULL != pretend_time_str) + { + if (GNUNET_OK != GNUNET_STRINGS_fancy_time_to_absolute (pretend_time_str, &now)) + { + fprintf (stderr, "timestamp invalid\n"); + return 1; + } + } + else + { + now = GNUNET_TIME_absolute_get (); + } + ROUND_TO_SECS (now, abs_value_us); + + kcfg = TALER_MINT_config_load (mintdir); + if (NULL == kcfg) + { + fprintf (stderr, "can't load mint configuration\n"); + return 1; + } + + if (NULL == masterkeyfile) + { + fprintf (stderr, "master key file not given\n"); + return 1; + } + master_priv = GNUNET_CRYPTO_eddsa_key_create_from_file (masterkeyfile); + if (NULL == master_priv) + { + fprintf (stderr, "master key invalid\n"); + return 1; + } + + master_pub = GNUNET_new (struct GNUNET_CRYPTO_EddsaPublicKey); + GNUNET_CRYPTO_eddsa_key_get_public (master_priv, master_pub); + + // check if key from file matches the one from the configuration + { + struct GNUNET_CRYPTO_EddsaPublicKey master_pub_from_cfg; + if (GNUNET_OK != TALER_configuration_get_data (kcfg, "mint", "master_pub", + &master_pub_from_cfg, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) + { + fprintf (stderr, "master key missing in configuration (mint.master_pub)\n"); + return 1; + } + if (0 != memcmp (master_pub, &master_pub_from_cfg, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) + { + fprintf (stderr, "Mismatch between key from mint configuration and master private key file from command line.\n"); + return 1; + } + } + + if (GNUNET_OK != mint_keys_update ()) + return 1; + return 0; +} + diff --git a/src/mint/taler-mint-reservemod.c b/src/mint/taler-mint-reservemod.c new file mode 100644 index 000000000..3dd94f84b --- /dev/null +++ b/src/mint/taler-mint-reservemod.c @@ -0,0 +1,215 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-reservemod.c + * @brief Modify reserves. + * @author Florian Dold + * @author Benedikt Mueller + */ + +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <libpq-fe.h> +#include "taler_util.h" +#include "taler_signatures.h" +#include "mint.h" + +static char *mintdir; +static struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub; +static struct GNUNET_CONFIGURATION_Handle *kcfg; +static PGconn *db_conn; + + + +/** + * Create a new or add to existing reserve. + * Fails if currencies do not match. + * + * @param denom denomination to add + * + * @return ... + */ +int +reservemod_add (struct TALER_Amount denom) +{ + PGresult *result; + { + const void *param_values[] = { reserve_pub }; + int param_lengths[] = {sizeof(struct GNUNET_CRYPTO_EddsaPublicKey)}; + int param_formats[] = {1}; + result = PQexecParams (db_conn, + "select balance_value, balance_fraction, balance_currency from reserves where reserve_pub=$1 limit 1;", + 1, NULL, (const char * const *) param_values, param_lengths, param_formats, 1); + } + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + fprintf (stderr, "Select failed: %s\n", PQresultErrorMessage (result)); + return GNUNET_SYSERR; + } + if (0 == PQntuples (result)) + { + struct GNUNET_TIME_AbsoluteNBO exnbo; + exnbo = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add ( GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_YEARS)); + + uint32_t value = htonl (denom.value); + uint32_t fraction = htonl (denom.fraction); + const void *param_values[] = { + reserve_pub, + &value, + &fraction, + denom.currency, + &exnbo}; + int param_lengths[] = {32, 4, 4, strlen(denom.currency), 8}; + int param_formats[] = {1, 1, 1, 1, 1}; + result = PQexecParams (db_conn, + "insert into reserves (reserve_pub, balance_value, balance_fraction, balance_currency, " + " expiration_date )" + "values ($1,$2,$3,$4,$5);", + 5, NULL, (const char **) param_values, param_lengths, param_formats, 1); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + fprintf (stderr, "Insert failed: %s\n", PQresultErrorMessage (result)); + return GNUNET_SYSERR; + } + } + else + { + struct TALER_Amount old_denom; + struct TALER_Amount new_denom; + struct TALER_AmountNBO new_denom_nbo; + int denom_indices[] = {0, 1, 2}; + int param_lengths[] = {4, 4, 32}; + int param_formats[] = {1, 1, 1}; + const void *param_values[] = { + &new_denom_nbo.value, + &new_denom_nbo.fraction, + reserve_pub + }; + + GNUNET_assert (GNUNET_OK == TALER_TALER_DB_extract_amount (result, 0, denom_indices, &old_denom)); + new_denom = TALER_amount_add (old_denom, denom); + new_denom_nbo = TALER_amount_hton (new_denom); + result = PQexecParams (db_conn, + "UPDATE reserves " + "SET balance_value = $1, balance_fraction = $2, " + " status_sig = NULL, status_sign_pub = NULL " + "WHERE reserve_pub = $3 ", + 3, NULL, (const char **) param_values, param_lengths, param_formats, 1); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + fprintf (stderr, "Update failed: %s\n", PQresultErrorMessage (result)); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + fprintf (stderr, "Update failed (updated '%s' tupes instead of '1')\n", + PQcmdTuples (result)); + return GNUNET_SYSERR; + } + + } + return GNUNET_OK; +} + + +/** + * The main function of the reservemod tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + static char *reserve_pub_str; + static char *add_str; + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"), + {'d', "mint-dir", "DIR", + "mint directory with keys to update", 1, + &GNUNET_GETOPT_set_filename, &mintdir}, + {'R', "reserve", "KEY", + "reserve (public key) to modify", 1, + &GNUNET_GETOPT_set_string, &reserve_pub_str}, + {'a', "add", "DENOM", + "value to add", 1, + &GNUNET_GETOPT_set_string, &add_str}, + GNUNET_GETOPT_OPTION_END + }; + char *TALER_MINT_db_connection_cfg_str; + + GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("taler-mint-keycheck", "WARNING", NULL)); + + if (GNUNET_GETOPT_run ("taler-mint-keyup", options, argc, argv) < 0) + return 1; + if (NULL == mintdir) + { + fprintf (stderr, "mint directory not given\n"); + return 1; + } + + reserve_pub = GNUNET_new (struct GNUNET_CRYPTO_EddsaPublicKey); + if ((NULL == reserve_pub_str) || + (GNUNET_OK != GNUNET_STRINGS_string_to_data (reserve_pub_str, + strlen (reserve_pub_str), + reserve_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)))) + { + fprintf (stderr, "reserve key invalid\n"); + return 1; + } + + kcfg = TALER_MINT_config_load (mintdir); + if (NULL == kcfg) + { + fprintf (stderr, "can't load mint configuration\n"); + return 1; + } + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg, "mint", "db", &TALER_MINT_db_connection_cfg_str)) + { + fprintf (stderr, "db configuration string not found\n"); + return 42; + } + db_conn = PQconnectdb (TALER_MINT_db_connection_cfg_str); + if (CONNECTION_OK != PQstatus (db_conn)) + { + fprintf (stderr, "db connection failed: %s\n", PQerrorMessage (db_conn)); + return 1; + } + + if (NULL != add_str) + { + struct TALER_Amount add_value; + if (GNUNET_OK != TALER_string_to_amount (add_str, &add_value)) + { + fprintf (stderr, "could not read value\n"); + return 1; + } + if (GNUNET_OK != reservemod_add (add_value)) + { + fprintf (stderr, "adding value failed\n"); + return 1; + } + } + return 0; +} + diff --git a/src/mint/test_mint_api.c b/src/mint/test_mint_api.c new file mode 100644 index 000000000..965d607f5 --- /dev/null +++ b/src/mint/test_mint_api.c @@ -0,0 +1,211 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint/test_mint_api.c + * @brief testcase to test mint's HTTP API interface + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#include "platform.h" +#include "taler_util.h" +#include "taler_mint_service.h" + +struct TALER_MINT_Context *ctx; + +struct TALER_MINT_Handle *mint; + +struct TALER_MINT_KeysGetHandle *dkey_get; + +struct TALER_MINT_DepositHandle *dh; + +static GNUNET_SCHEDULER_TaskIdentifier shutdown_task; + +static int result; + + +static void +do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + shutdown_task = GNUNET_SCHEDULER_NO_TASK; + if (NULL != dkey_get) + TALER_MINT_keys_get_cancel (dkey_get); + dkey_get = NULL; + if (NULL != dh) + TALER_MINT_deposit_submit_cancel (dh); + dh = NULL; + TALER_MINT_disconnect (mint); + mint = NULL; + TALER_MINT_cleanup (ctx); + ctx = NULL; +} + + +/** + * Callbacks of this type are used to serve the result of submitting a deposit + * permission object to a mint + * + * @param cls closure + * @param status 1 for successful deposit, 2 for retry, 0 for failure + * @param obj the received JSON object; can be NULL if it cannot be constructed + * from the reply + * @param emsg in case of unsuccessful deposit, this contains a human readable + * explanation. + */ +static void +deposit_status (void *cls, + int status, + json_t *obj, + char *emsg) +{ + char *json_enc; + + dh = NULL; + json_enc = NULL; + if (NULL != obj) + { + json_enc = json_dumps (obj, JSON_INDENT(2)); + fprintf (stderr, "%s", json_enc); + } + if (1 == status) + result = GNUNET_OK; + else + GNUNET_break (0); + if (NULL != emsg) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Deposit failed: %s\n", emsg); + GNUNET_SCHEDULER_shutdown (); +} +/** + * Functions of this type are called to signal completion of an asynchronous call. + * + * @param cls closure + * @param emsg if the asynchronous call could not be completed due to an error, + * this parameter contains a human readable error message + */ +static void +cont (void *cls, const char *emsg) +{ + json_t *dp; + char rnd_32[32]; + char rnd_64[64]; + char *enc_32; + char *enc_64; + + GNUNET_assert (NULL == cls); + dkey_get = NULL; + if (NULL != emsg) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "%s\n", emsg); + + enc_32 = TALER_data_to_string_alloc (rnd_32, sizeof (rnd_32)); + enc_64 = TALER_data_to_string_alloc (rnd_64, sizeof (rnd_64)); + dp = json_pack ("{s:s s:o s:s s:s s:s s:s s:s s:s s:s s:s}", + "type", "DIRECT_DEPOSIT", + "wire", json_pack ("{s:s}", "type", "SEPA"), + "C", enc_32, + "K", enc_32, + "ubsig", enc_64, + "M", enc_32, + "H_a", enc_64, + "H_wire", enc_64, + "csig", enc_64, + "m", "B1C5GP2RB1C5G"); + GNUNET_free (enc_32); + GNUNET_free (enc_64); + dh = TALER_MINT_deposit_submit_json (mint, + deposit_status, + NULL, + dp); + json_decref (dp); +} + + +/** + * Functions of this type are called to provide the retrieved signing and + * denomination keys of the mint. No TALER_MINT_*() functions should be called + * in this callback. + * + * @param cls closure passed to TALER_MINT_keys_get() + * @param sign_keys NULL-terminated array of pointers to the mint's signing + * keys. NULL if no signing keys are retrieved. + * @param denom_keys NULL-terminated array of pointers to the mint's + * denomination keys; will be NULL if no signing keys are retrieved. + */ +static void +read_denom_key (void *cls, + struct TALER_MINT_SigningPublicKey **sign_keys, + struct TALER_MINT_DenomPublicKey **denom_keys) +{ + unsigned int cnt; + GNUNET_assert (NULL == cls); +#define ERR(cond) do { if(!(cond)) break; GNUNET_break (0); return; } while (0) + ERR (NULL == sign_keys); + ERR (NULL == denom_keys); + for (cnt = 0; NULL != sign_keys[cnt]; cnt++) + GNUNET_free (sign_keys[cnt]); + ERR (0 == cnt); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Read %u signing keys\n", cnt); + GNUNET_free (sign_keys); + for (cnt = 0; NULL != denom_keys[cnt]; cnt++) + GNUNET_free (denom_keys[cnt]); + ERR (0 == cnt); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Read %u denomination keys\n", cnt); + GNUNET_free (denom_keys); +#undef ERR + return; +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param config configuration + */ +static void +run (void *cls, char *const *args, const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *config) +{ + ctx = TALER_MINT_init (); + mint = TALER_MINT_connect (ctx, "localhost", 4241, NULL); + GNUNET_assert (NULL != mint); + dkey_get = TALER_MINT_keys_get (mint, + &read_denom_key, NULL, + &cont, NULL); + GNUNET_assert (NULL != dkey_get); + shutdown_task = + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, 5), + &do_shutdown, NULL); +} + +int +main (int argc, char * const *argv) +{ + static struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_END + }; + + result = GNUNET_SYSERR; + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, argv, "test-mint-api", + gettext_noop + ("Testcase to test mint's HTTP API interface"), + options, &run, NULL)) + return 3; + return (GNUNET_OK == result) ? 0 : 1; +} diff --git a/src/mint/test_mint_common.c b/src/mint/test_mint_common.c new file mode 100644 index 000000000..b7cad3ea4 --- /dev/null +++ b/src/mint/test_mint_common.c @@ -0,0 +1,83 @@ +/* + This file is part of TALER + (C) 2014 GNUnet e. V. (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint/test_mint_common.c + * @brief test cases for some functions in mint/mint_common.c + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#include "platform.h" +#include "gnunet/gnunet_util_lib.h" +#include "taler_rsa.h" +#include "mint.h" + +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + +int +main (int argc, const char *const argv[]) +{ + struct TALER_MINT_DenomKeyIssue dki; + struct TALER_RSA_PrivateKeyBinaryEncoded *enc; + struct TALER_MINT_DenomKeyIssue dki_read; + struct TALER_RSA_PrivateKeyBinaryEncoded *enc_read; + char *tmpfile; + + int ret; + + ret = 1; + enc = NULL; + enc_read = NULL; + tmpfile = NULL; + dki.denom_priv = NULL; + dki_read.denom_priv = NULL; + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &dki.signature, + sizeof (dki) - offsetof (struct TALER_MINT_DenomKeyIssue, + signature)); + dki.denom_priv = TALER_RSA_key_create (); + EXITIF (NULL == (enc = TALER_RSA_encode_key (dki.denom_priv))); + EXITIF (NULL == (tmpfile = GNUNET_DISK_mktemp ("test_mint_common"))); + EXITIF (GNUNET_OK != TALER_MINT_write_denom_key (tmpfile, &dki)); + EXITIF (GNUNET_OK != TALER_MINT_read_denom_key (tmpfile, &dki_read)); + EXITIF (NULL == (enc_read = TALER_RSA_encode_key (dki_read.denom_priv))); + EXITIF (enc->len != enc_read->len); + EXITIF (0 != memcmp (enc, + enc_read, + ntohs(enc->len))); + EXITIF (0 != memcmp (&dki.signature, + &dki_read.signature, + sizeof (dki) - offsetof (struct TALER_MINT_DenomKeyIssue, + signature))); + ret = 0; + + EXITIF_exit: + GNUNET_free_non_null (enc); + if (NULL != tmpfile) + { + (void) unlink (tmpfile); + GNUNET_free (tmpfile); + } + GNUNET_free_non_null (enc_read); + if (NULL != dki.denom_priv) + TALER_RSA_key_free (dki.denom_priv); + if (NULL != dki_read.denom_priv) + TALER_RSA_key_free (dki_read.denom_priv); + return ret; +} diff --git a/src/mint/test_mint_deposits.c b/src/mint/test_mint_deposits.c new file mode 100644 index 000000000..776bc15d2 --- /dev/null +++ b/src/mint/test_mint_deposits.c @@ -0,0 +1,149 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file mint/test_mint_deposits.c + * @brief testcase for mint deposits + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ + +#include "platform.h" +#include <libpq-fe.h> +#include <gnunet/gnunet_util_lib.h> +#include "mint_db.h" + +#define DB_URI "postgres:///taler" + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +/** + * DB connection handle + */ +static PGconn *conn; + +/** + * Should we not interact with a temporary table? + */ +static int persistent; + +/** + * Testcase result + */ +static int result; + + +static void +do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + if (NULL != conn) + PQfinish (conn); + conn = NULL; +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param config configuration + */ +static void +run (void *cls, char *const *args, const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *config) +{ + static const char wire[] = "{" + "\"type\":\"SEPA\"," + "\"IBAN\":\"DE67830654080004822650\"," + "\"NAME\":\"GNUNET E.V\"," + "\"BIC\":\"GENODEF1SRL\"" + "}"; + struct Deposit *deposit; + struct Deposit *q_deposit; + uint64_t transaction_id; + + deposit = NULL; + q_deposit = NULL; + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, + &do_shutdown, NULL); + EXITIF (NULL == (conn = PQconnectdb(DB_URI))); + EXITIF (GNUNET_OK != TALER_MINT_DB_init_deposits (conn, !persistent)); + EXITIF (GNUNET_OK != TALER_MINT_DB_prepare_deposits (conn)); + deposit = GNUNET_malloc (sizeof (struct Deposit) + sizeof (wire)); + /* Makeup a random coin public key */ + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + deposit, + sizeof (struct Deposit)); + /* Makeup a random 64bit transaction ID */ + transaction_id = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, + UINT64_MAX); + deposit->transaction_id = GNUNET_htonll (transaction_id); + /* Random amount */ + deposit->amount.value = + htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX)); + deposit->amount.fraction = + htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX)); + strcpy (deposit->amount.currency, "EUR"); + /* Copy wireformat */ + (void) memcpy (deposit->wire, wire, sizeof (wire)); + EXITIF (GNUNET_OK != TALER_MINT_DB_insert_deposit (conn, + deposit)); + EXITIF (GNUNET_OK != TALER_MINT_DB_get_deposit (conn, + &deposit->coin_pub, + &q_deposit)); + EXITIF (0 != memcmp (deposit, + q_deposit, + sizeof (struct Deposit) - offsetof(struct Deposit, + wire))); + EXITIF (transaction_id != GNUNET_ntohll (q_deposit->transaction_id)); + result = GNUNET_OK; + + EXITIF_exit: + GNUNET_free_non_null (deposit); + GNUNET_free_non_null (q_deposit); + GNUNET_SCHEDULER_shutdown (); + return; +} + + +int main(int argc, char *const argv[]) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + {'T', "persist", NULL, + gettext_noop ("Use a persistent database table instead of a temporary one"), + GNUNET_NO, &GNUNET_GETOPT_set_one, &persistent}, + GNUNET_GETOPT_OPTION_END + }; + + + persistent = GNUNET_NO; + result = GNUNET_SYSERR; + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, argv, + "test-mint-deposits", + "testcase for mint deposits", + options, &run, NULL)) + return 3; + return (GNUNET_OK == result) ? 0 : 1; +} diff --git a/src/mint/test_mint_nayapaisa.ecc b/src/mint/test_mint_nayapaisa.ecc Binary files differnew file mode 100644 index 000000000..942110b5c --- /dev/null +++ b/src/mint/test_mint_nayapaisa.ecc diff --git a/src/mint/test_mint_nayapaisa/README b/src/mint/test_mint_nayapaisa/README new file mode 100644 index 000000000..fce5e0180 --- /dev/null +++ b/src/mint/test_mint_nayapaisa/README @@ -0,0 +1 @@ +This directory is a template for the mint directory. diff --git a/src/mint/test_mint_nayapaisa/config/mint-common.conf b/src/mint/test_mint_nayapaisa/config/mint-common.conf new file mode 100644 index 000000000..c1fede7a2 --- /dev/null +++ b/src/mint/test_mint_nayapaisa/config/mint-common.conf @@ -0,0 +1,6 @@ +[mint] +db = postgres:///taler +port = 4241 +master_pub = 6ZE0HEY2M0FWP61M0470HYBF4K6RRD5DP54372PD2TN9N9VX2VJG +refresh_security_parameter = 3 + diff --git a/src/mint/test_mint_nayapaisa/config/mint-keyup.conf b/src/mint/test_mint_nayapaisa/config/mint-keyup.conf new file mode 100644 index 000000000..1542d1a63 --- /dev/null +++ b/src/mint/test_mint_nayapaisa/config/mint-keyup.conf @@ -0,0 +1,79 @@ +[mint_keys] + +# how long is one signkey valid? +signkey_duration = 4 weeks + +# how long do we generate denomination and signing keys +# ahead of time? +lookahead_sign = 32 weeks 1 day + +# how long do we provide to clients denomination and signing keys +# ahead of time? +lookahead_provide = 4 weeks 1 day + +# what coin types do we have available? +coin_types = default_eur_ct_10 default_eur_5 default_eur_10 default_eur_1000 + + + +[mint_denom_duration_overlap] +default_eur_ct_10 = 5 minutes +default_eur_5 = 5 minutes +default_eur_10 = 5 minutes +default_eur_1000 = 5 minutes + + + +[mint_denom_value] +default_eur_ct_10 = EUR:0.10 +default_eur_5 = EUR:5 +default_eur_10 = EUR:10 +default_eur_1000 = EUR:1000 + + + +[mint_denom_duration_withdraw] +default_eur_ct_10 = 7 days +default_eur_5 = 7 days +default_eur_10 = 7 days +default_eur_1000 = 1 day + + + +[mint_denom_duration_spend] +default_eur_ct_10 = 30 days +default_eur_5 = 30 days +default_eur_10 = 30 days +default_eur_1000 = 30 day + + + +[mint_denom_fee_withdraw] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + +[mint_denom_fee_deposit] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + + +[mint_denom_fee_refresh] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + + +[mint_denom_kappa] +default_eur_ct_10 = 3 +default_eur_5 = 3 +default_eur_10 = 3 +default_eur_1000 = 5 + diff --git a/src/mint/test_mint_nyadirahim.ecc b/src/mint/test_mint_nyadirahim.ecc new file mode 100644 index 000000000..9db920894 --- /dev/null +++ b/src/mint/test_mint_nyadirahim.ecc @@ -0,0 +1 @@ +ÃWÜBøÐfØ ŽµrØñ·•ŠÊÐŒ„Ú:ß½V»j
\ No newline at end of file diff --git a/src/mint/test_mint_nyadirahim/README b/src/mint/test_mint_nyadirahim/README new file mode 100644 index 000000000..fce5e0180 --- /dev/null +++ b/src/mint/test_mint_nyadirahim/README @@ -0,0 +1 @@ +This directory is a template for the mint directory. diff --git a/src/mint/test_mint_nyadirahim/config/mint-common.conf b/src/mint/test_mint_nyadirahim/config/mint-common.conf new file mode 100644 index 000000000..c4d528634 --- /dev/null +++ b/src/mint/test_mint_nyadirahim/config/mint-common.conf @@ -0,0 +1,6 @@ +[mint] +db = postgres:///taler +port = 4241 +master_pub = 7995WKK71KPKTBBMA5BHNBSZFGNRZPYNXDJMQ8EK86V9598H03TG +refresh_security_parameter = 3 + diff --git a/src/mint/test_mint_nyadirahim/config/mint-keyup.conf b/src/mint/test_mint_nyadirahim/config/mint-keyup.conf new file mode 100644 index 000000000..1542d1a63 --- /dev/null +++ b/src/mint/test_mint_nyadirahim/config/mint-keyup.conf @@ -0,0 +1,79 @@ +[mint_keys] + +# how long is one signkey valid? +signkey_duration = 4 weeks + +# how long do we generate denomination and signing keys +# ahead of time? +lookahead_sign = 32 weeks 1 day + +# how long do we provide to clients denomination and signing keys +# ahead of time? +lookahead_provide = 4 weeks 1 day + +# what coin types do we have available? +coin_types = default_eur_ct_10 default_eur_5 default_eur_10 default_eur_1000 + + + +[mint_denom_duration_overlap] +default_eur_ct_10 = 5 minutes +default_eur_5 = 5 minutes +default_eur_10 = 5 minutes +default_eur_1000 = 5 minutes + + + +[mint_denom_value] +default_eur_ct_10 = EUR:0.10 +default_eur_5 = EUR:5 +default_eur_10 = EUR:10 +default_eur_1000 = EUR:1000 + + + +[mint_denom_duration_withdraw] +default_eur_ct_10 = 7 days +default_eur_5 = 7 days +default_eur_10 = 7 days +default_eur_1000 = 1 day + + + +[mint_denom_duration_spend] +default_eur_ct_10 = 30 days +default_eur_5 = 30 days +default_eur_10 = 30 days +default_eur_1000 = 30 day + + + +[mint_denom_fee_withdraw] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + +[mint_denom_fee_deposit] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + + +[mint_denom_fee_refresh] +default_eur_ct_10 = EUR:0.01 +default_eur_5 = EUR:0.01 +default_eur_10 = EUR:0.01 +default_eur_1000 = EUR:0.01 + + + +[mint_denom_kappa] +default_eur_ct_10 = 3 +default_eur_5 = 3 +default_eur_10 = 3 +default_eur_1000 = 5 + |