From 57d1f08dbca256f5fe16d57b29bfa523dec8f6c4 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 8 Jan 2015 18:37:20 +0100 Subject: -initial import for mint --- src/.gitignore | 9 + src/Makefile.am | 2 + src/include/Makefile.am | 7 + src/include/platform.h | 56 + src/include/taler_db_lib.h | 132 ++ src/include/taler_json_lib.h | 101 ++ src/include/taler_microhttpd_lib.h | 119 ++ src/include/taler_mint_service.h | 303 ++++ src/include/taler_rsa.h | 357 ++++ src/include/taler_signatures.h | 106 ++ src/include/taler_types.h | 120 ++ src/include/taler_util.h | 255 +++ src/mint/.gitignore | 6 + src/mint/Makefile.am | 131 ++ src/mint/mint.h | 198 +++ src/mint/mint_api.c | 1121 ++++++++++++ src/mint/mint_common.c | 283 +++ src/mint/mint_db.c | 1838 ++++++++++++++++++++ src/mint/mint_db.h | 344 ++++ src/mint/taler-mint-dbinit.c | 285 +++ src/mint/taler-mint-httpd.c | 376 ++++ src/mint/taler-mint-httpd.h | 106 ++ src/mint/taler-mint-httpd_deposit.c | 270 +++ src/mint/taler-mint-httpd_deposit.h | 48 + src/mint/taler-mint-httpd_keys.c | 512 ++++++ src/mint/taler-mint-httpd_keys.h | 155 ++ src/mint/taler-mint-httpd_mhd.c | 300 ++++ src/mint/taler-mint-httpd_mhd.h | 132 ++ src/mint/taler-mint-httpd_refresh.c | 1497 ++++++++++++++++ src/mint/taler-mint-httpd_refresh.h | 103 ++ src/mint/taler-mint-httpd_withdraw.c | 400 +++++ src/mint/taler-mint-httpd_withdraw.h | 65 + src/mint/taler-mint-keycheck.c | 169 ++ src/mint/taler-mint-keyup.c | 657 +++++++ src/mint/taler-mint-reservemod.c | 215 +++ src/mint/test_mint_api.c | 211 +++ src/mint/test_mint_common.c | 83 + src/mint/test_mint_deposits.c | 149 ++ src/mint/test_mint_nayapaisa.ecc | Bin 0 -> 32 bytes src/mint/test_mint_nayapaisa/README | 1 + .../test_mint_nayapaisa/config/mint-common.conf | 6 + .../test_mint_nayapaisa/config/mint-keyup.conf | 79 + src/mint/test_mint_nyadirahim.ecc | 1 + src/mint/test_mint_nyadirahim/README | 1 + .../test_mint_nyadirahim/config/mint-common.conf | 6 + .../test_mint_nyadirahim/config/mint-keyup.conf | 79 + src/util/Makefile.am | 39 + src/util/db.c | 196 +++ src/util/json.c | 194 +++ src/util/microhttpd.c | 417 +++++ src/util/misc.supp | 28 + src/util/rsa.c | 925 ++++++++++ src/util/test_hash_context.c | 48 + src/util/test_rsa.c | 112 ++ src/util/util.c | 528 ++++++ 55 files changed, 13881 insertions(+) create mode 100644 src/.gitignore create mode 100644 src/Makefile.am create mode 100644 src/include/Makefile.am create mode 100644 src/include/platform.h create mode 100644 src/include/taler_db_lib.h create mode 100644 src/include/taler_json_lib.h create mode 100644 src/include/taler_microhttpd_lib.h create mode 100644 src/include/taler_mint_service.h create mode 100644 src/include/taler_rsa.h create mode 100644 src/include/taler_signatures.h create mode 100644 src/include/taler_types.h create mode 100644 src/include/taler_util.h create mode 100644 src/mint/.gitignore create mode 100644 src/mint/Makefile.am create mode 100644 src/mint/mint.h create mode 100644 src/mint/mint_api.c create mode 100644 src/mint/mint_common.c create mode 100644 src/mint/mint_db.c create mode 100644 src/mint/mint_db.h create mode 100644 src/mint/taler-mint-dbinit.c create mode 100644 src/mint/taler-mint-httpd.c create mode 100644 src/mint/taler-mint-httpd.h create mode 100644 src/mint/taler-mint-httpd_deposit.c create mode 100644 src/mint/taler-mint-httpd_deposit.h create mode 100644 src/mint/taler-mint-httpd_keys.c create mode 100644 src/mint/taler-mint-httpd_keys.h create mode 100644 src/mint/taler-mint-httpd_mhd.c create mode 100644 src/mint/taler-mint-httpd_mhd.h create mode 100644 src/mint/taler-mint-httpd_refresh.c create mode 100644 src/mint/taler-mint-httpd_refresh.h create mode 100644 src/mint/taler-mint-httpd_withdraw.c create mode 100644 src/mint/taler-mint-httpd_withdraw.h create mode 100644 src/mint/taler-mint-keycheck.c create mode 100644 src/mint/taler-mint-keyup.c create mode 100644 src/mint/taler-mint-reservemod.c create mode 100644 src/mint/test_mint_api.c create mode 100644 src/mint/test_mint_common.c create mode 100644 src/mint/test_mint_deposits.c create mode 100644 src/mint/test_mint_nayapaisa.ecc create mode 100644 src/mint/test_mint_nayapaisa/README create mode 100644 src/mint/test_mint_nayapaisa/config/mint-common.conf create mode 100644 src/mint/test_mint_nayapaisa/config/mint-keyup.conf create mode 100644 src/mint/test_mint_nyadirahim.ecc create mode 100644 src/mint/test_mint_nyadirahim/README create mode 100644 src/mint/test_mint_nyadirahim/config/mint-common.conf create mode 100644 src/mint/test_mint_nyadirahim/config/mint-keyup.conf create mode 100644 src/util/Makefile.am create mode 100644 src/util/db.c create mode 100644 src/util/json.c create mode 100644 src/util/microhttpd.c create mode 100644 src/util/misc.supp create mode 100644 src/util/rsa.c create mode 100644 src/util/test_hash_context.c create mode 100644 src/util/test_rsa.c create mode 100644 src/util/util.c (limited to 'src') diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 000000000..90ea1a047 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,9 @@ +*.o +*.deps +*.libs +*.lo +*.la +*.log +*.trs +*/__pycache__ +test-* \ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 000000000..485c4f9d7 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,2 @@ +AM_CPPFLAGS = -I$(top_srcdir)/src/include +SUBDIRS = include util mint diff --git a/src/include/Makefile.am b/src/include/Makefile.am new file mode 100644 index 000000000..d10d6d70e --- /dev/null +++ b/src/include/Makefile.am @@ -0,0 +1,7 @@ +EXTRA_DIST = \ + platform.h \ + taler_blind.h \ + taler_signatures.h \ + taler_types.h \ + taler_util.h \ + taler_rsa.h diff --git a/src/include/platform.h b/src/include/platform.h new file mode 100644 index 000000000..4cba7abfd --- /dev/null +++ b/src/include/platform.h @@ -0,0 +1,56 @@ +/* + This file is part of TALER + (C) 2014 Chrisitan 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 +*/ + +/** + * @file include/platform.h + * @brief This file contains the includes and definitions which are used by the + * rest of the modules + * @author Sree Harsha Totakura + */ + +#ifndef PLATFORM_H_ +#define PLATFORM_H_ + +/* Include our configuration header */ +#ifndef HAVE_USED_CONFIG_H +# define HAVE_USED_CONFIG_H +# ifdef HAVE_CONFIG_H +# include "taler_config.h" +# endif +#endif + + +#if (GNUNET_EXTRA_LOGGING >= 1) +#define VERBOSE(cmd) cmd +#else +#define VERBOSE(cmd) do { break; }while(0) +#endif + +/* Include the features available for GNU source */ +#define _GNU_SOURCE + +/* Include GNUnet's platform file */ +#include + +/* Do not use shortcuts for gcrypt mpi */ +#define GCRYPT_NO_MPI_MACROS 1 + +/* Do not use deprecated functions from gcrypt */ +#define GCRYPT_NO_DEPRECATED 1 + +#endif /* PLATFORM_H_ */ + +/* end of platform.h */ diff --git a/src/include/taler_db_lib.h b/src/include/taler_db_lib.h new file mode 100644 index 000000000..41b46264e --- /dev/null +++ b/src/include/taler_db_lib.h @@ -0,0 +1,132 @@ +/* + 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 +*/ + + +/** + * @file include/taler_db_lib.h + * @brief helper functions for DB interactions + * @author Sree Harsha Totakura + * @author Florian Dold + */ + +#ifndef TALER_DB_LIB_H_ +#define TALER_DB_LIB_H_ + +#include +#include "taler_util.h" + +#define TALER_DB_QUERY_PARAM_END { NULL, 0, 0 } +#define TALER_DB_QUERY_PARAM_PTR(x) { (x), sizeof (*(x)), 1 } +#define TALER_DB_QUERY_PARAM_PTR_SIZED(x, s) { (x), (s), 1 } + + +#define TALER_DB_RESULT_SPEC_END { NULL, 0, NULL } +#define TALER_DB_RESULT_SPEC(name, dst) { (void *) (dst), sizeof (*(dst)), (name) } +#define TALER_DB_RESULT_SPEC_SIZED(name, dst, s) { (void *) (dst), (s), (name) } + + +/** + * Description of a DB query parameter. + */ +struct TALER_DB_QueryParam +{ + /** + * Data or NULL + */ + const void *data; + /** + * Size of 'data' + */ + size_t size; + /** + * Non-null if this is not the last parameter. + * This allows for null as sentinal value. + */ + int more; +}; + + +/** + * Description of a DB result cell. + */ +struct TALER_DB_ResultSpec +{ + /** + * Destination for the data. + */ + void *dst; + + /** + * Allowed size for the data. + */ + size_t dst_size; + + /** + * Field name of the desired result. + */ + char *fname; +}; + + +/** + * Execute a prepared statement. + */ +PGresult * +TALER_DB_exec_prepared (PGconn *db_conn, + const char *name, + const struct TALER_DB_QueryParam *params); + + +/** + * Extract results from a query result according to the given specification. + * If colums are NULL, the destination is not modified, and GNUNET_NO + * is returned. + * + * @return + * GNUNET_YES if all results could be extracted + * GNUNET_NO if at least one result was NULL + * GNUNET_SYSERR if a result was invalid (non-existing field) + */ +int +TALER_DB_extract_result (PGresult *result, struct TALER_DB_ResultSpec *rs, int row); + + +int +TALER_DB_field_isnull (PGresult *result, + int row, + const char *fname); + + +int +TALER_DB_extract_amount_nbo (PGresult *result, + int row, + const char *val_name, + const char *frac_name, + const char *curr_name, + struct TALER_AmountNBO *r_amount_nbo); + + +int +TALER_DB_extract_amount (PGresult *result, + int row, + const char *val_name, + const char *frac_name, + const char *curr_name, + struct TALER_Amount *r_amount); + +#endif /* TALER_DB_LIB_H_ */ + +/* end of include/taler_db_lib.h */ diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h new file mode 100644 index 000000000..b224c4b33 --- /dev/null +++ b/src/include/taler_json_lib.h @@ -0,0 +1,101 @@ +/* + 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 +*/ + +/** + * @file include/taler_json_lib.h + * @brief helper functions for JSON processing using libjansson + * @author Sree Harsha Totakura + */ + +#ifndef TALER_JSON_LIB_H_ +#define TALER_JSON_LIB_H_ + +#include + + +/** + * Convert a TALER amount to a JSON + * object. + * + * @param amount the amount + * @return a json object describing the amount + */ +json_t * +TALER_JSON_from_amount (struct TALER_Amount amount); + + +/** + * Convert absolute timestamp to a json string. + * + * @param the time stamp + * @return a json string with the timestamp in @a stamp + */ +json_t * +TALER_JSON_from_abs (struct GNUNET_TIME_Absolute stamp); + + + +/** + * Convert binary data to a JSON string + * with the base32crockford encoding. + * + * @param data binary data + * @param size size of @a data in bytes + * @return json string that encodes @a data + */ +json_t * +TALER_JSON_from_data (const void *data, size_t size); + + +/** + * Parse given JSON object to Amount + * + * @param json the json object representing Amount + * @param r_amount where the amount has to be written + * @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error + */ +int +TALER_JSON_to_amount (json_t *json, + struct TALER_Amount *r_amount); + +/** + * Parse given JSON object to absolute time. + * + * @param json the json object representing absolute time in seconds + * @param r_abs where the time has to be written + * @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error + */ +int +TALER_JSON_to_abs (json_t *json, + struct GNUNET_TIME_Absolute *r_abs); + +/** + * Parse given JSON object to data + * + * @param json the json object representing data + * @param out the pointer to hold the parsed data. + * @param out_size the size of r_data. + * @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error + */ +int +TALER_JSON_to_data (json_t *json, + void *out, + size_t out_size); + + +#endif /* TALER_JSON_LIB_H_ */ + +/* End of taler_json_lib.h */ diff --git a/src/include/taler_microhttpd_lib.h b/src/include/taler_microhttpd_lib.h new file mode 100644 index 000000000..da601401f --- /dev/null +++ b/src/include/taler_microhttpd_lib.h @@ -0,0 +1,119 @@ + + +#ifndef TALER_MICROHTTPD_LIB_H_ +#define TALER_MICROHTTPD_LIB_H_ + + +#include +#include + + +/** + * Constants for JSON navigation description. + */ +enum +{ + /** + * Access a field. + * Param: const char * + */ + JNAV_FIELD, + /** + * Access an array index. + * Param: int + */ + JNAV_INDEX, + /** + * Return base32crockford encoded data of + * constant size. + * Params: (void *, size_t) + */ + JNAV_RET_DATA, + /** + * Return base32crockford encoded data of + * variable size. + * Params: (void **, size_t *) + */ + JNAV_RET_DATA_VAR, + /** + * Return a json object, which must be + * of the given type (JSON_* type constants, + * or -1 for any type). + * Params: (int, json_t **) + */ + JNAV_RET_TYPED_JSON +}; + + + +/** + * Send JSON object as response. Decreases + * the reference count of the JSON object. + * + * @param connection the MHD connection + * @param json the json object + * @param status_code the http status code + * @return MHD result code (MHD_YES on success) + */ +int +send_response_json (struct MHD_Connection *connection, + json_t *json, + unsigned int status_code); + + +/** + * Send a JSON object via an MHD connection, + * specified with the JANSSON pack syntax (see json_pack). + * + * @param connection connection to send the JSON over + * @param http_code HTTP status for the response + * @param fmt format string for pack + * @param ... varargs + * @return MHD_YES on success or MHD_NO on error + */ +int +request_send_json_pack (struct MHD_Connection *connection, + unsigned int http_code, + const char *fmt, ...); + + +/** + * Process a POST request containing a JSON object. + * + * @param connection the MHD connection + * @param con_cs the closure (contains a 'struct Buffer *') + * @param upload_data the POST data + * @param upload_data_size the POST data size + * @param json the JSON object for a completed request + * + * @returns + * GNUNET_YES if json object was parsed + * GNUNET_NO is request incomplete or invalid + * GNUNET_SYSERR on internal error + */ +int +process_post_json (struct MHD_Connection *connection, + void **con_cls, + const char *upload_data, + size_t *upload_data_size, + json_t **json); + + +/** + * Navigate through a JSON tree. + * + * Sends an error response if navigation is impossible (i.e. + * the JSON object is invalid) + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param ... navigation specification (see JNAV_*) + * @return GNUNET_YES if navigation was successful + * GNUNET_NO if json is malformed, error response was generated + * GNUNET_SYSERR on internal error + */ +int +request_json_require_nav (struct MHD_Connection *connection, + const json_t *root, ...); + +#endif /* TALER_MICROHTTPD_LIB_H_ */ diff --git a/src/include/taler_mint_service.h b/src/include/taler_mint_service.h new file mode 100644 index 000000000..ee3b30e39 --- /dev/null +++ b/src/include/taler_mint_service.h @@ -0,0 +1,303 @@ +/* + 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 +*/ + +/** + * @file include/taler_mint_service.h + * @brief C interface to the mint's HTTP API + * @author Sree Harsha Totakura + */ + +#ifndef _TALER_MINT_SERVICE_H +#define _TALER_MINT_SERVICE_H + +#include "taler_rsa.h" +#include "taler_util.h" +#include + +/** + * Handle to this library context + */ +struct TALER_MINT_Context; + +/** + * Handle to the mint + */ +struct TALER_MINT_Handle; + +/** + * Mint's signature key + */ +struct TALER_MINT_SigningPublicKey +{ + /** + * The signing public key + */ + struct GNUNET_CRYPTO_EddsaPublicKey key; + + /** + * Validity start time + */ + struct GNUNET_TIME_Absolute valid_from; + + /** + * Validity expiration time + */ + struct GNUNET_TIME_Absolute valid_until; +}; + + +/** + * Mint's denomination key + */ +struct TALER_MINT_DenomPublicKey +{ + /** + * The public key + */ + struct TALER_RSA_PublicKeyBinaryEncoded key; + + /** + * Timestamp indicating when the denomination key becomes valid + */ + struct GNUNET_TIME_Absolute valid_from; + + /** + * Timestamp indicating when the denomination key can’t be used anymore to + * withdraw new coins. + */ + struct GNUNET_TIME_Absolute withdraw_valid_until; + + /** + * Timestamp indicating when coins of this denomination become invalid. + */ + struct GNUNET_TIME_Absolute deposit_valid_until; + + /** + * The value of this denomination + */ + struct TALER_Amount value; + + /** + * The applicable fee for withdrawing a coin of this denomination + */ + struct TALER_Amount fee_withdraw; + + /** + * The applicable fee to spend a coin of this denomination + */ + struct TALER_Amount fee_deposit; + + /** + *The applicable fee to refresh a coin of this denomination + */ + struct TALER_Amount fee_refresh; +}; + + +/** + * Initialise a context. A context should be used for each thread and should + * not be shared among multiple threads. + * + * @return the context + */ +struct TALER_MINT_Context * +TALER_MINT_init (); + + +/** + * 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); + + +/** + * 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. If port is + * given as 0, ports 80 or 443 are chosen depending on @a url. + * @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); + +/** + * Disconnect from the mint + * + * @param mint the mint handle + */ +void +TALER_MINT_disconnect (struct TALER_MINT_Handle *mint); + + +/** + * A handle to get the keys of a mint + */ +struct TALER_MINT_KeysGetHandle; + +/** + * 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 + */ +typedef void (*TALER_MINT_ContinuationCallback) (void *cls, + const char *emsg); + +/** + * 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. + */ +typedef void (*TALER_MINT_KeysGetCallback) (void *cls, + struct TALER_MINT_SigningPublicKey **sign_keys, + struct TALER_MINT_DenomPublicKey **denom_keys); + + +/** + * Get the signing and denomination key of the mint. + * + * @param mint handle to the mint + * @param cb the callback to call with the keys + * @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); + +/** + * 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); + + +/** + * A Deposit Handle + */ +struct TALER_MINT_DepositHandle; + + +/** + * 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. + */ +typedef void (*TALER_MINT_DepositResultCallback) (void *cls, + int status, + json_t *obj, + char *emsg); + +/** + * 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); + + +#if 0 +/** + * 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 coin the public key of the coin + * @param denom_key denomination key of the mint which is used to blind-sign the + * coin + * @param ubsig the mint's unblinded signature + * @param transaction_id transaction identifier + * @param amount the amount to deposit + * @param merchant_pub the public key of the merchant + * @param h_contract hash of the contract + * @param h_wire hash of the wire format used + * @param csig signature of the coin over the transaction_id, amount, + * merchant_pub, h_contract and, h_wire + * @param wire_obj the wireformat object corresponding to h_wire + * @return a handle for this request + */ +struct TALER_MINT_DepositHandle * +TALER_MINT_deposit_submit_json_ (struct TALER_MINT_Handle *mint, + TALER_MINT_DepositResultCallback *cb, + void *cls, + struct GNUNET_CRYPTO_EddsaPublicKey *coin_pub, + struct TALER_BLIND_SigningPublicKey *denom_pub, + struct TALER_BLIND_Signature *ubsig, + uint64_t transaction_id, + struct TALER_Amount *amount, + struct GNUNET_CRYPTO_EddsaPublicKey *merchant_pub, + struct GNUNET_HashCode *h_contract, + struct GNUNET_HashCode *h_wire, + struct GNUNET_CRYPTO_EddsaSignature *csig, + json_t *wire_obj); +#endif + + +/** + * 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); + +#endif /* _TALER_MINT_SERVICE_H */ diff --git a/src/include/taler_rsa.h b/src/include/taler_rsa.h new file mode 100644 index 000000000..1ed530013 --- /dev/null +++ b/src/include/taler_rsa.h @@ -0,0 +1,357 @@ +/* + 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 +*/ + +/** + * @file include/taler_rsa.h + * @brief RSA key management utilities. Some code is taken from gnunet-0.9.5a + * @author Sree Harsha Totakura + * + * Authors of the gnunet code: + * Christian Grothoff + * Krista Bennett + * Gerd Knorr + * Ioana Patrascu + * Tzvetan Horozov + */ + +#ifndef TALER_RSA_H +#define TALER_RSA_H + +#include +#include + +/** + * Length of an RSA KEY (n,e,len), 2048 bit (=256 octests) key n, 2 byte e + */ +#define TALER_RSA_KEY_LENGTH 258 + +/** + * @brief Length of RSA encrypted data (2048 bit) + * + * We currently do not handle encryption of data + * that can not be done in a single call to the + * RSA methods (read: large chunks of data). + * We should never need that, as we can use + * the GNUNET_CRYPTO_hash for larger pieces of data for signing, + * and for encryption, we only need to encode sessionkeys! + */ +#define TALER_RSA_DATA_ENCODING_LENGTH 256 + +/** + * The private information of an RSA key pair. + */ +struct TALER_RSA_PrivateKey; + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * GNUnet mandates a certain format for the encoding + * of private RSA key information that is provided + * by the RSA implementations. This format is used + * to serialize a private RSA key (typically when + * writing it to disk). + */ +struct TALER_RSA_PrivateKeyBinaryEncoded +{ + /** + * Total size of the structure, in bytes, in big-endian! + */ + uint16_t len GNUNET_PACKED; + uint16_t sizen GNUNET_PACKED; /* in big-endian! */ + uint16_t sizee GNUNET_PACKED; /* in big-endian! */ + uint16_t sized GNUNET_PACKED; /* in big-endian! */ + uint16_t sizep GNUNET_PACKED; /* in big-endian! */ + uint16_t sizeq GNUNET_PACKED; /* in big-endian! */ + uint16_t sizedmp1 GNUNET_PACKED; /* in big-endian! */ + uint16_t sizedmq1 GNUNET_PACKED; /* in big-endian! */ + /* followed by the actual values */ +}; +GNUNET_NETWORK_STRUCT_END + + +/** + * @brief an RSA signature + */ +struct TALER_RSA_Signature +{ + unsigned char sig[TALER_RSA_DATA_ENCODING_LENGTH]; +}; + +GNUNET_NETWORK_STRUCT_BEGIN +/** + * @brief header of what an RSA signature signs + * this must be followed by "size - 8" bytes of + * the actual signed data + */ +struct TALER_RSA_SignaturePurpose +{ + /** + * How many bytes does this signature sign? + * (including this purpose header); in network + * byte order (!). + */ + uint32_t size GNUNET_PACKED; + + /** + * What does this signature vouch for? This + * must contain a GNUNET_SIGNATURE_PURPOSE_XXX + * constant (from gnunet_signatures.h). In + * network byte order! + */ + uint32_t purpose GNUNET_PACKED; + +}; + + +struct TALER_RSA_BlindedSignaturePurpose +{ + unsigned char data[TALER_RSA_DATA_ENCODING_LENGTH]; +}; + + +/** + * @brief A public key. + */ +struct TALER_RSA_PublicKeyBinaryEncoded +{ + /** + * In big-endian, must be GNUNET_CRYPTO_RSA_KEY_LENGTH+4 + */ + uint16_t len GNUNET_PACKED; + + /** + * Size of n in key; in big-endian! + */ + uint16_t sizen GNUNET_PACKED; + + /** + * The key itself, contains n followed by e. + */ + unsigned char key[TALER_RSA_KEY_LENGTH]; + + /** + * Padding (must be 0) + */ + uint16_t padding GNUNET_PACKED; +}; + +GNUNET_NETWORK_STRUCT_END + +/** + * Create a new private key. Caller must free return value. + * + * @return fresh private key + */ +struct TALER_RSA_PrivateKey * +TALER_RSA_key_create (); + + +/** + * Free memory occupied by the private key. + * + * @param key pointer to the memory to free + */ +void +TALER_RSA_key_free (struct TALER_RSA_PrivateKey *key); + + +/** + * Encode the private key in a format suitable for + * storing it into a file. + * @return encoding of the private key + */ +struct TALER_RSA_PrivateKeyBinaryEncoded * +TALER_RSA_encode_key (const struct TALER_RSA_PrivateKey *hostkey); + + +/** + * Extract the public key of the given private key. + * + * @param priv the private key + * @param pub where to write the public key + */ +void +TALER_RSA_key_get_public (const struct TALER_RSA_PrivateKey *priv, + struct TALER_RSA_PublicKeyBinaryEncoded *pub); + + +/** + * Decode the private key from the data-format back + * to the "normal", internal format. + * + * @param buf the buffer where the private key data is stored + * @param len the length of the data in 'buffer' + * @return NULL on error + */ +struct TALER_RSA_PrivateKey * +TALER_RSA_decode_key (const char *buf, uint16_t len); + + +/** + * Convert a public key to a string. + * + * @param pub key to convert + * @return string representing 'pub' + */ +char * +TALER_RSA_public_key_to_string (const struct TALER_RSA_PublicKeyBinaryEncoded *pub); + + +/** + * Convert a string representing a public key to a public key. + * + * @param enc encoded public key + * @param enclen number of bytes in enc (without 0-terminator) + * @param pub where to store the public key + * @return GNUNET_OK on success + */ +int +TALER_RSA_public_key_from_string (const char *enc, + size_t enclen, + struct TALER_RSA_PublicKeyBinaryEncoded *pub); + + +/** + * Sign a given block.h + * + * @param key private key to use for the signing + * @param msg the message + * @param size the size of the message + * @param sig where to write the signature + * @return GNUNET_SYSERR on error, GNUNET_OK on success + */ +int +TALER_RSA_sign (const struct TALER_RSA_PrivateKey *key, + const void *msg, + size_t size, + struct TALER_RSA_Signature *sig); + + +/** + * Verify signature with the given hash. + * + * @param hash the hash code to verify against the signature + * @param sig signature that is being validated + * @param publicKey public key of the signer + * @returns GNUNET_OK if ok, GNUNET_SYSERR if invalid + */ +int +TALER_RSA_hash_verify (const struct GNUNET_HashCode *hash, + const struct TALER_RSA_Signature *sig, + const struct TALER_RSA_PublicKeyBinaryEncoded *publicKey); + + +/** + * Verify signature on the given message + * + * @param msg the message + * @param size the size of the message + * @param sig signature that is being validated + * @param publicKey public key of the signer + * @returns GNUNET_OK if ok, GNUNET_SYSERR if invalid + */ +int +TALER_RSA_verify (const void *msg, size_t size, + const struct TALER_RSA_Signature *sig, + const struct TALER_RSA_PublicKeyBinaryEncoded *publicKey); + +/** + * Key used to blind a message + */ +struct TALER_RSA_BlindingKey; + +/** + * Create a blinding key + * + * @return the newly created blinding key + */ +struct TALER_RSA_BlindingKey * +TALER_RSA_blinding_key_create (); + + +/** + * Destroy a blinding key + * + * @param bkey the blinding key to destroy + */ +void +TALER_RSA_blinding_key_destroy (struct TALER_RSA_BlindingKey *bkey); + + +/** + * Binary encoding for TALER_RSA_BlindingKey + */ +struct TALER_RSA_BlindingKeyBinaryEncoded +{ + unsigned char data[TALER_RSA_DATA_ENCODING_LENGTH]; +}; + + +/** + * Encode a blinding key + * + * @param bkey the blinding key to encode + * @param bkey_enc where to store the encoded binary key + * @return #GNUNET_OK upon successful encoding; #GNUNET_SYSERR upon failure + */ +int +TALER_RSA_blinding_key_encode (struct TALER_RSA_BlindingKey *bkey, + struct TALER_RSA_BlindingKeyBinaryEncoded *bkey_enc); + + +/** + * Decode a blinding key from its encoded form + * + * @param bkey_enc the encoded blinding key + * @return the decoded blinding key; NULL upon error + */ +struct TALER_RSA_BlindingKey * +TALER_RSA_blinding_key_decode (struct TALER_RSA_BlindingKeyBinaryEncoded *bkey_enc); + + +/** + * Blinds the given message with the given blinding key + * + * @param msg the message + * @param size the size of the message + * @param bkey the blinding key + * @param pkey the public key of the signer + * @return the blinding signature purpose; NULL upon any error + */ +struct TALER_RSA_BlindedSignaturePurpose * +TALER_RSA_message_blind (const void *msg, size_t size, + struct TALER_RSA_BlindingKey *bkey, + struct TALER_RSA_PublicKeyBinaryEncoded *pkey); + + +/** + * Unblind a signature made on blinding signature purpose. The signature + * purpose should have been generated with TALER_RSA_message_blind() function. + * + * @param sig the signature made on the blinded signature purpose + * @param bkey the blinding key used to blind the signature purpose + * @param pkey the public key of the signer + * @return GNUNET_SYSERR upon error; GNUNET_OK upon success. + */ +int +TALER_RSA_unblind (struct TALER_RSA_Signature *sig, + struct TALER_RSA_BlindingKey *bkey, + struct TALER_RSA_PublicKeyBinaryEncoded *pkey); + +#endif /* TALER_RSA_H */ + +/* end of include/taler_rsa.h */ diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h new file mode 100644 index 000000000..8c142f61f --- /dev/null +++ b/src/include/taler_signatures.h @@ -0,0 +1,106 @@ +/* + 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 +*/ + +/** + * @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 + */ + +#ifndef TALER_SIGNATURES_H +#define TALER_SIGNATURES_H + +/** + * Purpose for signing public keys signed + * by the mint master key. + */ +#define TALER_SIGNATURE_MASTER_SIGNKEY 1 + +/** + * Purpose for denomination keys signed + * by the mint master key. + */ +#define TALER_SIGNATURE_MASTER_DENOM 2 + +/** + * Purpose for the state of a reserve, + * signed by the mint's signing key. + */ +#define TALER_SIGNATURE_RESERVE_STATUS 3 + +/** + * Signature where the reserve key + * confirms a withdraw request. + */ +#define TALER_SIGNATURE_WITHDRAW 4 + +/** + * Signature where the refresh session confirms + * the list of melted coins and requested denominations. + */ +#define TALER_SIGNATURE_REFRESH_MELT 5 + +/** + * Signature where the refresh session confirms + * the commits. + */ +#define TALER_SIGNATURE_REFRESH_COMMIT 6 + +/** + * Signature where the mint (current signing key) + * confirms the list of blind session keys. + */ +#define TALER_SIGNATURE_REFRESH_MELT_RESPONSE 7 + +/** + * Signature where the mint (current signing key) + * confirms the no-reveal index for cut-and-choose. + */ +#define TALER_SIGNATURE_REFRESH_COMMIT_RESPONSE 8 + +/** + * Signature where coins confirm that they want + * to be melted into a certain session. + */ +#define TALER_SIGNATURE_REFRESH_MELT_CONFIRM 9 + +/***********************/ +/* Merchant signatures */ +/***********************/ + +/** + * Signature where the merchant confirms a contract + */ +#define TALER_SIGNATURE_MERCHANT_CONTRACT 101 + +/*********************/ +/* Wallet signatures */ +/*********************/ + +/** + * Signature made by the wallet of a user to confirm a deposit permission + */ +#define TALER_SIGNATURE_DEPOSIT 201 + +/** + * Signature made by the wallet of a user to confirm a incremental deposit permission + */ +#define TALER_SIGNATURE_INCREMENTAL_DEPOSIT 202 + +#endif + diff --git a/src/include/taler_types.h b/src/include/taler_types.h new file mode 100644 index 000000000..c6c2c0209 --- /dev/null +++ b/src/include/taler_types.h @@ -0,0 +1,120 @@ +/** + * @file include/types.h + * @brief This files defines the various data and message types in TALER. + * @author Sree Harsha Totakura + * @author Florian Dold + */ + +#ifndef TYPES_H_ +#define TYPES_H_ + +#include "taler_rsa.h" + + +/** + * Public information about a coin. + */ +struct TALER_CoinPublicInfo +{ + /** + * The coin's public key. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey coin_pub; + + /* + * The public key signifying the coin's denomination. + */ + struct TALER_RSA_PublicKeyBinaryEncoded denom_pub; + + /** + * Signature over coin_pub by denom_pub. + */ + struct TALER_RSA_Signature denom_sig; +}; + + +/** + * Request to withdraw coins from a reserve. + */ +struct TALER_WithdrawRequest +{ + /** + * Signature over the rest of the message + * by the withdraw public key. + */ + struct GNUNET_CRYPTO_EddsaSignature sig; + + /** + * Purpose must be TALER_SIGNATURE_WITHDRAW. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Reserve public key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; + + /** + * Denomination public key for the coin that is withdrawn. + */ + struct TALER_RSA_PublicKeyBinaryEncoded denomination_pub; + + /** + * Purpose containing coin's blinded public key. + */ + struct TALER_RSA_BlindedSignaturePurpose coin_envelope; +}; + + + +/** + * Data type for messages + */ +struct TALER_MessageHeader +{ + /** + * The type of the message in Network-byte order (NBO) + */ + uint16_t type; + + /** + * The size of the message in NBO + */ + uint16_t size; +}; + +/*****************/ +/* Message types */ +/*****************/ + +/** + * The message type of a blind signature + */ +#define TALER_MSG_TYPE_BLINDED_SIGNATURE 1 + +/** + * The message type of a blinded message + */ +#define TALER_MSG_TYPE_BLINDED_MESSAGE 2 + +/** + * The message type of an unblinded signature + * @FIXME: Not currently used + */ +#define TALER_MSG_TYPE_UNBLINDED_SIGNATURE 3 + +/** + * The type of a blinding residue message + * @FIXME: Not currently used + */ +#define TALER_MSG_TYPE_BLINDING_RESIDUE 4 + +/** + * The type of a message containing the blinding factor + */ +#define TALER_MSG_TYPE_BLINDING_FACTOR 5 + + +#endif /* TYPES_H_ */ + +/* end of include/types.h */ diff --git a/src/include/taler_util.h b/src/include/taler_util.h new file mode 100644 index 000000000..a8a7c2013 --- /dev/null +++ b/src/include/taler_util.h @@ -0,0 +1,255 @@ +/* + 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 +*/ + +/** + * @file include/taler_util.h + * @brief Interface for common utility functions + * @author Sree Harsha Totakura + */ + +#ifndef UTIL_H_ +#define UTIL_H_ + +#include +#include + +/* Define logging functions */ +#define LOG_DEBUG(...) \ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__) + +#define LOG_WARNING(...) \ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, __VA_ARGS__) + +#define LOG_ERROR(...) \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, __VA_ARGS__) + + +/** + * Tests a given as assertion and if failed prints it as a warning with the + * given reason + * + * @param EXP the expression to test as assertion + * @param reason string to print as warning + */ +#define TALER_assert_as(EXP, reason) \ + do { \ + if (EXP) break; \ + LOG_ERROR("%s at %s:%d\n", reason, __FILE__, __LINE__); \ + abort(); \ + } while(0) + + + +/** + * Log an error message at log-level 'level' that indicates + * a failure of the command 'cmd' with the message given + * by gcry_strerror(rc). + */ +#define LOG_GCRY_ERROR(cmd, rc) do { LOG_ERROR("`%s' failed at %s:%d with error: %s\n", cmd, __FILE__, __LINE__, gcry_strerror(rc)); } while(0) + + +#define TALER_gcry_ok(cmd) \ + do {int rc; rc = cmd; if (!rc) break; LOG_ERROR("A Gcrypt call failed at %s:%d with error: %s\n", __FILE__, __LINE__, gcry_strerror(rc)); abort(); } while (0) + + +#define TALER_CURRENCY_LEN 4 + + +GNUNET_NETWORK_STRUCT_BEGIN + +struct TALER_AmountNBO +{ + uint32_t value; + uint32_t fraction; + char currency[TALER_CURRENCY_LEN]; +}; + +GNUNET_NETWORK_STRUCT_END + +struct TALER_HashContext +{ + gcry_md_hd_t hd; +}; + + + +/** + * Representation of monetary value in a given currency. + */ +struct TALER_Amount +{ + /** + * Value (numerator of fraction) + */ + uint32_t value; + /** + * Fraction (denominator of fraction) + */ + uint32_t fraction; + /** + * Currency string, left adjusted and padded with zeros. + */ + char currency[4]; +}; + + +/** + * Initialize Gcrypt library. + */ +void +TALER_gcrypt_init(); + + +/** + * Generate a ECC private key. + * + * @return the s-expression representing the generated ECC private key; NULL + * upon error + */ +gcry_sexp_t +TALER_genkey (); + + +/** + * Parse denomination description, in the format "T : V : F". + * + * @param str denomination description + * @param denom denomination to write the result to + * @return GNUNET_OK if the string is a valid denomination specification, + * GNUNET_SYSERR if it is invalid. + */ +int +TALER_string_to_amount (const char *str, struct TALER_Amount *denom); + + +/** + * FIXME + */ +struct TALER_AmountNBO +TALER_amount_hton (struct TALER_Amount d); + + +/** + * FIXME + */ +struct TALER_Amount +TALER_amount_ntoh (struct TALER_AmountNBO dn); + +/** + * Compare the value/fraction of two amounts. Does not compare the currency, + * i.e. comparing amounts with the same value and fraction but different + * currency would return 0. + * + * @param a1 first amount + * @param a2 second amount + * @return result of the comparison + */ +int +TALER_amount_cmp (struct TALER_Amount a1, struct TALER_Amount a2); + + +/** + * Perform saturating subtraction of amounts. + * + * @param a1 amount to subtract from + * @param a2 amount to subtract + * @return (a1-a2) or 0 if a2>=a1 + */ +struct TALER_Amount +TALER_amount_subtract (struct TALER_Amount a1, struct TALER_Amount a2); + + +/** + * Perform saturating addition of amounts + * + * @param a1 first amount to add + * @param a2 second amount to add + * @return sum of a1 and a2 + */ +struct TALER_Amount +TALER_amount_add (struct TALER_Amount a1, struct TALER_Amount a2); + + +/** + * Normalize the given amount. + * + * @param amout amount to normalize + * @return normalized amount + */ +struct TALER_Amount +TALER_amount_normalize (struct TALER_Amount amount); + + +/** + * Convert amount to string. + * + * @param amount amount to convert to string + * @return freshly allocated string representation + */ +char * +TALER_amount_to_string (struct TALER_Amount amount); + + +/** + * Return the base32crockford encoding of the given buffer. + * + * The returned string will be freshly allocated, and must be free'd + * with GNUNET_free. + * + * @param buffer with data + * @param size size of the buffer + * @return freshly allocated, null-terminated string + */ +char * +TALER_data_to_string_alloc (const void *buf, size_t size); + + +/** + * Get encoded binary data from a configuration. + * + * @return GNUNET_OK on success + * GNUNET_NO is the value does not exist + * GNUNET_SYSERR on encoding error + */ +int +TALER_configuration_get_data (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, const char *option, + void *buf, size_t buf_size); + + + + +int +TALER_refresh_decrypt (const void *input, size_t input_size, const struct GNUNET_HashCode *secret, void *result); + +int +TALER_refresh_encrypt (const void *input, size_t input_size, const struct GNUNET_HashCode *secret, void *result); + + + +void +TALER_hash_context_start (struct TALER_HashContext *hc); + + +void +TALER_hash_context_read (struct TALER_HashContext *hc, void *buf, size_t size); + + +void +TALER_hash_context_finish (struct TALER_HashContext *hc, + struct GNUNET_HashCode *r_hash); + +#endif 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 +*/ + +/** + * @file taler_mint.h + * @brief Common functionality for the mint + * @author Florian Dold + * @author Benedikt Mueller + */ + +#ifndef _MINT_H +#define _MINT_H + +#include +#include +#include +#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 + +*/ + +/** + * @file mint/mint_api.c + * @brief Implementation of the client interface to mint's HTTP API + * @author Sree Harsha Totakura + */ + +#include "platform.h" +#include +#include +#include +#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 +*/ + +/** + * @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 +*/ + +/** + * @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 + +/** + * 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 +*/ + +/** + * @file mint/mint_db.h + * @brief Mint-specific database access + * @author Florian Dold + */ + +#ifndef _NEURO_MINT_DB_H +#define _NEURO_MINT_DB_H + +#include +#include +#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 +*/ + +/** + * @file taler-mint-dbinit.c + * @brief Create tables for the mint database. + * @author Florian Dold + */ + +#include "platform.h" +#include +#include +#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 +*/ + +/** + * @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 +#include +#include +#include +#include +#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", + "404: not found", 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 +*/ +/** + * @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 +*/ +/** + * @file taler-mint-httpd_deposit.c + * @brief Handle /deposit requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include +#include +#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 +*/ +/** + * @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 +#include +#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 +*/ +/** + * @file taler-mint-httpd_keys.c + * @brief Handle /keys requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include +#include +#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 +*/ +/** + * @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 +#include +#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 +*/ + +/** + * @file taler-mint-httpd_mhd.c + * @brief helpers for MHD interaction + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include +#include +#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 +*/ + +/** + * @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 +#include +#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 +*/ +/** + * @file taler-mint-httpd_refresh.c + * @brief Handle /refresh/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include +#include +#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 +*/ +/** + * @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 +#include +#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 +*/ +/** + * @file taler-mint-httpd_withdraw.c + * @brief Handle /withdraw/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include +#include +#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 +*/ +/** + * @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 +#include +#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 +*/ + +/** + * @file taler-mint-keycheck.c + * @brief Check mint keys for validity. + * @author Florian Dold + * @author Benedikt Mueller + */ + +#include +#include +#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 +*/ + +/** + * @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 +#include +#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 +*/ + +/** + * @file taler-mint-reservemod.c + * @brief Modify reserves. + * @author Florian Dold + * @author Benedikt Mueller + */ + +#include "platform.h" +#include +#include +#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 +*/ + +/** + * @file mint/test_mint_api.c + * @brief testcase to test mint's HTTP API interface + * @author Sree Harsha Totakura + */ + +#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 +*/ + +/** + * @file mint/test_mint_common.c + * @brief test cases for some functions in mint/mint_common.c + * @author Sree Harsha Totakura + */ + +#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 +*/ + +/** + * @file mint/test_mint_deposits.c + * @brief testcase for mint deposits + * @author Sree Harsha Totakura + */ + +#include "platform.h" +#include +#include +#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 new file mode 100644 index 000000000..942110b5c Binary files /dev/null and b/src/mint/test_mint_nayapaisa.ecc differ 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 + diff --git a/src/util/Makefile.am b/src/util/Makefile.am new file mode 100644 index 000000000..f935802a6 --- /dev/null +++ b/src/util/Makefile.am @@ -0,0 +1,39 @@ +AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS) $(POSTGRESQL_CPPFLAGS) + +lib_LTLIBRARIES = \ + libtalerutil.la + +libtalerutil_la_SOURCES = \ + util.c \ + json.c \ + db.c \ + microhttpd.c \ + rsa.c + +libtalerutil_la_LIBADD = \ + -lgnunetutil \ + $(LIBGCRYPT_LIBS) \ + -ljansson \ + -lmicrohttpd \ + -lpq + +libtalerutil_la_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) \ + -version-info 0:0:0 \ + -export-dynamic -no-undefined + +check_PROGRAMS = \ + test-hash-context \ + test-rsa + +TESTS = \ + $(check_PROGRAMS) + +test_hash_context_SOURCES = test_hash_context.c +test_hash_context_CPPFLAGS = $(AM_CPPFLAGS) $(LIBGCRYPT_CFLAGS) +test_hash_context_LDADD = libtalerutil.la \ + -lgnunetutil $(LIBGCRYPT_LIBS) + +test_rsa_SOURCES = test_rsa.c +test_rsa_LDADD = libtalerutil.la \ + -lgnunetutil $(LIBGCRYPT_LIBS) diff --git a/src/util/db.c b/src/util/db.c new file mode 100644 index 000000000..a0b234a06 --- /dev/null +++ b/src/util/db.c @@ -0,0 +1,196 @@ +/* + 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 +*/ + + +/** + * @file util/db.c + * @brief helper functions for DB interactions + * @author Sree Harsha Totakura + * @author Florian Dold + */ + +#include "platform.h" +#include +#include "taler_db_lib.h" + + +/** + * Execute a prepared statement. + */ +PGresult * +TALER_DB_exec_prepared (PGconn *db_conn, + const char *name, + const struct TALER_DB_QueryParam *params) +{ + unsigned len; + unsigned i; + + /* count the number of parameters */ + + { + const struct TALER_DB_QueryParam *x; + for (len = 0, x = params; + x->more; + len +=1, x += 1); + } + + /* new scope to allow stack allocation without alloca */ + + { + void *param_values[len]; + int param_lengths[len]; + int param_formats[len]; + + for (i = 0; i < len; i += 1) + { + param_values[i] = (void *) params[i].data; + param_lengths[i] = params[i].size; + param_formats[i] = 1; + } + return PQexecPrepared (db_conn, name, len, + (const char **) param_values, param_lengths, param_formats, 1); + } +} + + +/** + * Extract results from a query result according to the given specification. + * If colums are NULL, the destination is not modified, and GNUNET_NO + * is returned. + * + * @return + * GNUNET_YES if all results could be extracted + * GNUNET_NO if at least one result was NULL + * GNUNET_SYSERR if a result was invalid (non-existing field) + */ +int +TALER_DB_extract_result (PGresult *result, + struct TALER_DB_ResultSpec *rs, + int row) +{ + int had_null = GNUNET_NO; + + for (; NULL != rs->fname; rs += 1) + { + int fnum; + fnum = PQfnumber (result, rs->fname); + if (fnum < 0) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "field '%s' does not exist in result\n", rs->fname); + return GNUNET_SYSERR; + } + + /* if a field is null, continue but + * remember that we now return a different result */ + + if (PQgetisnull (result, row, fnum)) + { + had_null = GNUNET_YES; + continue; + } + const char *res; + if (rs->dst_size != PQgetlength (result, row, fnum)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "field '%s' has wrong size (got %u, expected %u)\n", + rs->fname, (int) PQgetlength (result, row, fnum), (int) rs->dst_size); + return GNUNET_SYSERR; + } + res = PQgetvalue (result, row, fnum); + GNUNET_assert (NULL != res); + memcpy (rs->dst, res, rs->dst_size); + } + if (GNUNET_YES == had_null) + return GNUNET_NO; + return GNUNET_YES; +} + + +int +TALER_DB_field_isnull (PGresult *result, + int row, + const char *fname) +{ + int fnum; + fnum = PQfnumber (result, fname); + GNUNET_assert (fnum >= 0); + if (PQgetisnull (result, row, fnum)) + return GNUNET_YES; + return GNUNET_NO; +} + + +int +TALER_DB_extract_amount_nbo (PGresult *result, + int row, + const char *val_name, + const char *frac_name, + const char *curr_name, + struct TALER_AmountNBO *r_amount_nbo) +{ + int val_num; + int frac_num; + int curr_num; + int len; + + GNUNET_assert (NULL != strstr (val_name, "_val")); + GNUNET_assert (NULL != strstr (frac_name, "_frac")); + GNUNET_assert (NULL != strstr (curr_name, "_curr")); + + val_num = PQfnumber (result, val_name); + GNUNET_assert (val_num >= 0); + frac_num = PQfnumber (result, frac_name); + GNUNET_assert (frac_num >= 0); + curr_num = PQfnumber (result, curr_name); + GNUNET_assert (curr_num >= 0); + + r_amount_nbo->value = *(uint32_t *) PQgetvalue (result, row, val_num); + r_amount_nbo->fraction = *(uint32_t *) PQgetvalue (result, row, frac_num); + memset (r_amount_nbo->currency, 0, TALER_CURRENCY_LEN); + // FIXME: overflow? + len = PQgetlength (result, row, curr_num); + len = GNUNET_MIN (TALER_CURRENCY_LEN, len); + memcpy (r_amount_nbo->currency, PQgetvalue (result, row, curr_num), len); + r_amount_nbo->currency[TALER_CURRENCY_LEN - 1] = '\0'; + + return GNUNET_OK; +} + + +int +TALER_DB_extract_amount (PGresult *result, + int row, + const char *val_name, + const char *frac_name, + const char *curr_name, + struct TALER_Amount *r_amount) +{ + struct TALER_AmountNBO amount_nbo; + + (void) + TALER_DB_extract_amount_nbo (result, + row, + val_name, + frac_name, + curr_name, + &amount_nbo); + r_amount->value = ntohl (amount_nbo.value); + r_amount->fraction = ntohl (amount_nbo.fraction); + (void) strncpy (r_amount->currency, amount_nbo.currency, TALER_CURRENCY_LEN); + + return GNUNET_OK; +} + +/* end of util/db.c */ diff --git a/src/util/json.c b/src/util/json.c new file mode 100644 index 000000000..269e6cf26 --- /dev/null +++ b/src/util/json.c @@ -0,0 +1,194 @@ +/* + 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 +*/ + +/** + * @file util/json.c + * @brief helper functions for JSON processing using libjansson + * @author Sree Harsha Totakura + */ + +#include "platform.h" +#include +#include "taler_util.h" +#include "taler_json_lib.h" + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + +/** + * Print JSON parsing related error information + */ +#define WARN_JSON(error) \ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \ + "JSON parsing failed at %s:%u: %s (%s)", \ + __FILE__, __LINE__, error.text, error.source) + +/** + * Shorthand for JSON parsing related exit jumps. + */ +#define UNPACK_EXITIF(cond) \ + do { \ + if (cond) { WARN_JSON(error); goto EXITIF_exit; } \ + } while (0) + +/** + * Convert a TALER amount to a JSON + * object. + * + * @param amount the amount + * @return a json object describing the amount + */ +json_t * +TALER_JSON_from_amount (struct TALER_Amount amount) +{ + json_t *j; + j = json_pack ("{s: s, s:I, s:I}", + "currency", amount.currency, + "value", (json_int_t) amount.value, + "fraction", (json_int_t) amount.fraction); + GNUNET_assert (NULL != j); + return j; +} + + +/** + * Convert absolute timestamp to a json string. + * + * @param the time stamp + * @return a json string with the timestamp in @a stamp + */ +json_t * +TALER_JSON_from_abs (struct GNUNET_TIME_Absolute stamp) +{ + json_t *j; + char *mystr; + int ret; + ret = GNUNET_asprintf (&mystr, "%llu", + (long long) (stamp.abs_value_us / (1000 * 1000))); + GNUNET_assert (ret > 0); + j = json_string (mystr); + GNUNET_free (mystr); + return j; +} + + + +/** + * Convert binary data to a JSON string + * with the base32crockford encoding. + * + * @param data binary data + * @param size size of @a data in bytes + * @return json string that encodes @a data + */ +json_t * +TALER_JSON_from_data (const void *data, size_t size) +{ + char *buf; + json_t *json; + buf = TALER_data_to_string_alloc (data, size); + json = json_string (buf); + GNUNET_free (buf); + return json; +} + + +/** + * Parse given JSON object to Amount + * + * @param json the json object representing Amount + * @param r_amount where the amount has to be written + * @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error + */ +int +TALER_JSON_to_amount (json_t *json, + struct TALER_Amount *r_amount) +{ + char *currency; + json_int_t value; + json_int_t fraction; + json_error_t error; + + UNPACK_EXITIF (0 != json_unpack_ex (json, &error, JSON_STRICT, + "{s:s, s:I, s:I}", + "curreny", ¤cy, + "value", &value, + "fraction", &fraction)); + EXITIF (3 < strlen (currency)); + r_amount->value = (uint32_t) value; + r_amount->fraction = (uint32_t) fraction; + return GNUNET_OK; + + EXITIF_exit: + return GNUNET_SYSERR; +} + + +/** + * Parse given JSON object to Amount + * + * @param json the json object representing Amount + * @param r_amount where the amount has to be written + * @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error + */ +int +TALER_JSON_to_abs (json_t *json, + struct GNUNET_TIME_Absolute *abs) +{ + const char *str; + unsigned long long abs_value_s; + + GNUNET_assert (NULL != abs); + EXITIF (NULL == (str = json_string_value (json))); + EXITIF (1 > sscanf (str, "%llu", &abs_value_s)); + abs->abs_value_us = abs_value_s * 1000 * 1000; + return GNUNET_OK; + + EXITIF_exit: + return GNUNET_SYSERR; +} + +/** + * Parse given JSON object to data + * + * @param json the json object representing data + * @param out the pointer to hold the parsed data. + * @param out_size the size of r_data. + * @return GNUNET_OK upon successful parsing; GNUNET_SYSERR upon error + */ +int +TALER_JSON_to_data (json_t *json, + void *out, + size_t out_size) +{ + const char *enc; + unsigned int len; + + EXITIF (NULL == (enc = json_string_value (json))); + len = strlen (enc); + EXITIF ((((len * 5) / 8) + ((((len * 5) % 8) == 0) ? 0 : 1)) == out_size); + EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (enc, len, out, out_size)); + return GNUNET_OK; + EXITIF_exit: + return GNUNET_SYSERR; +} + +/* End of util/json.c */ diff --git a/src/util/microhttpd.c b/src/util/microhttpd.c new file mode 100644 index 000000000..f3bea74f8 --- /dev/null +++ b/src/util/microhttpd.c @@ -0,0 +1,417 @@ +#include "platform.h" +#include +#include "taler_microhttpd_lib.h" + + + +/** + * Initial size for POST + * request buffer. + */ +#define REQUEST_BUFFER_INITIAL 1024 + +/** + * Maximum POST request size + */ +#define REQUEST_BUFFER_MAX (1024*1024) + + +/** + * Buffer for POST requests. + */ +struct Buffer +{ + /** + * Allocated memory + */ + char *data; + + /** + * Number of valid bytes in buffer. + */ + size_t fill; + + /** + * Number of allocated bytes in buffer. + */ + size_t alloc; +}; + + +/** + * Initialize a buffer. + * + * @param buf the buffer to initialize + * @param data the initial data + * @param data_size size of the initial data + * @param alloc_size size of the buffer + * @param max_size maximum size that the buffer can grow to + * @return a GNUnet result code + */ +static int +buffer_init (struct Buffer *buf, const void *data, size_t data_size, size_t alloc_size, size_t max_size) +{ + if (data_size > max_size || alloc_size > max_size) + return GNUNET_SYSERR; + if (data_size > alloc_size) + alloc_size = data_size; + buf->data = GNUNET_malloc (alloc_size); + memcpy (buf->data, data, data_size); + return GNUNET_OK; +} + + +/** + * Free the data in a buffer. Does *not* free + * the buffer object itself. + * + * @param buf buffer to de-initialize + */ +static void +buffer_deinit (struct Buffer *buf) +{ + GNUNET_free (buf->data); + buf->data = NULL; +} + + +/** + * Append data to a buffer, growing the buffer if necessary. + * + * @param buf the buffer to append to + * @param data the data to append + * @param size the size of @a data + * @param max_size maximum size that the buffer can grow to + * @return GNUNET_OK on success, + * GNUNET_NO if the buffer can't accomodate for the new data + * GNUNET_SYSERR on fatal error (out of memory?) + */ +static int +buffer_append (struct Buffer *buf, const void *data, size_t data_size, size_t max_size) +{ + if (buf->fill + data_size > max_size) + return GNUNET_NO; + if (data_size + buf->fill > buf->alloc) + { + char *new_buf; + size_t new_size = buf->alloc; + while (new_size < buf->fill + data_size) + new_size += 2; + if (new_size > max_size) + return GNUNET_NO; + new_buf = GNUNET_malloc (new_size); + memcpy (new_buf, buf->data, buf->fill); + buf->data = new_buf; + buf->alloc = new_size; + } + memcpy (buf->data + buf->fill, data, data_size); + buf->fill += data_size; + return GNUNET_OK; +} + + +/** + * Send JSON object as response. Decreases the reference count of the + * JSON object. + * + * @param connection the MHD connection + * @param json the json object + * @param status_code the http status code + * @return MHD result code + */ +int +send_response_json (struct MHD_Connection *connection, + json_t *json, + unsigned int status_code) +{ + struct MHD_Response *resp; + char *json_str; + + json_str = json_dumps (json, JSON_INDENT(2)); + json_decref (json); + resp = MHD_create_response_from_buffer (strlen (json_str), json_str, + MHD_RESPMEM_MUST_FREE); + if (NULL == resp) + return MHD_NO; + return MHD_queue_response (connection, status_code, resp); +} + + +/** + * Send a JSON object via an MHD connection, + * specified with the JANSSON pack syntax (see json_pack). + * + * @param connection connection to send the JSON over + * @param http_code HTTP status for the response + * @param fmt format string for pack + * @param ... varargs + * @return MHD_YES on success or MHD_NO on error + */ +int +request_send_json_pack (struct MHD_Connection *connection, + unsigned int http_code, + const char *fmt, ...) +{ + json_t *msg; + va_list argp; + int ret; + + va_start(argp, fmt); + msg = json_vpack_ex (NULL, 0, fmt, argp); + va_end(argp); + if (NULL == msg) + return MHD_NO; + ret = send_response_json (connection, msg, http_code); + json_decref (msg); + return ret; +} + + +/** + * Process a POST request containing a JSON object. + * + * @param connection the MHD connection + * @param con_cs the closure (contains a 'struct Buffer *') + * @param upload_data the POST data + * @param upload_data_size the POST data size + * @param json the JSON object for a completed request + * + * @returns + * GNUNET_YES if json object was parsed + * GNUNET_NO is request incomplete or invalid + * GNUNET_SYSERR on internal error + */ +int +process_post_json (struct MHD_Connection *connection, + void **con_cls, + const char *upload_data, + size_t *upload_data_size, + json_t **json) +{ + struct Buffer *r = *con_cls; + + if (NULL == *con_cls) + { + /* We are seeing a fresh POST request. */ + + r = GNUNET_new (struct Buffer); + if (GNUNET_OK != buffer_init (r, upload_data, *upload_data_size, + REQUEST_BUFFER_INITIAL, REQUEST_BUFFER_MAX)) + { + *con_cls = NULL; + buffer_deinit (r); + GNUNET_free (r); + return GNUNET_SYSERR; + } + *upload_data_size = 0; + *con_cls = r; + return GNUNET_NO; + } + if (0 != *upload_data_size) + { + /* We are seeing an old request with more data available. */ + + if (GNUNET_OK != buffer_append (r, upload_data, *upload_data_size, + REQUEST_BUFFER_MAX)) + { + /* Request too long or we're out of memory. */ + + *con_cls = NULL; + buffer_deinit (r); + GNUNET_free (r); + return GNUNET_SYSERR; + } + *upload_data_size = 0; + return GNUNET_NO; + } + + /* We have seen the whole request. */ + + *json = json_loadb (r->data, r->fill, 0, NULL); + buffer_deinit (r); + GNUNET_free (r); + if (NULL == *json) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Can't parse JSON request body\n"); + return request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + GNUNET_NO, GNUNET_SYSERR, + "{s:s}", + "error", "invalid json"); + } + *con_cls = NULL; + + return GNUNET_YES; +} + + +/** + * Navigate through a JSON tree. + * + * Sends an error response if navigation is impossible (i.e. + * the JSON object is invalid) + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param ... navigation specification (see JNAV_*) + * @return GNUNET_YES if navigation was successful + * GNUNET_NO if json is malformed, error response was generated + * GNUNET_SYSERR on internal error + */ +int +request_json_require_nav (struct MHD_Connection *connection, + const json_t *root, ...) +{ + va_list argp; + int ignore = GNUNET_NO; + // what's our current path from 'root'? + json_t *path; + + path = json_array (); + + va_start(argp, root); + + while (1) + { + int command = va_arg(argp, int); + switch (command) + { + case JNAV_FIELD: + { + const char *fname = va_arg(argp, const char *); + if (GNUNET_YES == ignore) + break; + json_array_append_new (path, json_string (fname)); + root = json_object_get (root, fname); + if (NULL == root) + { + + (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + 0, 0, + "{s:s,s:o}", + "error", "missing field in JSON", + "path", path); + ignore = GNUNET_YES; + break; + } + } + break; + case JNAV_INDEX: + { + int fnum = va_arg(argp, int); + if (GNUNET_YES == ignore) + break; + json_array_append_new (path, json_integer (fnum)); + root = json_array_get (root, fnum); + if (NULL == root) + { + (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + 0, 0, + "{s:s, s:o}", + "error", "missing index in JSON", + "path", path); + ignore = GNUNET_YES; + break; + } + } + break; + case JNAV_RET_DATA: + { + void *where = va_arg (argp, void *); + size_t len = va_arg (argp, size_t); + const char *str; + int res; + + va_end(argp); + if (GNUNET_YES == ignore) + return GNUNET_NO; + str = json_string_value (root); + if (NULL == str) + { + (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + 0, 0, + "{s:s, s:o}", + "error", "string expected", + "path", path); + return GNUNET_NO; + } + res = GNUNET_STRINGS_string_to_data (str, strlen (str), + where, len); + if (GNUNET_OK != res) + { + (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + 0, 0, + "{s:s,s:o}", + "error", "malformed binary data in JSON", + "path", path); + return GNUNET_NO; + } + return GNUNET_YES; + } + break; + case JNAV_RET_DATA_VAR: + { + void **where = va_arg (argp, void **); + size_t *len = va_arg (argp, size_t *); + const char *str; + + va_end(argp); + if (GNUNET_YES == ignore) + return GNUNET_NO; + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + *len = (strlen (str) * 5) / 8; + if (where != NULL) + { + int res; + *where = GNUNET_malloc (*len); + res = GNUNET_STRINGS_string_to_data (str, strlen (str), + *where, *len); + if (GNUNET_OK != res) + { + (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + 0, 0, + "{s:s, s:o}", + "error", "malformed binary data in JSON", + "path", path); + return GNUNET_NO; + } + } + return GNUNET_OK; + } + break; + case JNAV_RET_TYPED_JSON: + { + int typ = va_arg (argp, int); + const json_t **r_json = va_arg (argp, const json_t **); + + va_end(argp); + if (GNUNET_YES == ignore) + return GNUNET_NO; + if (typ != -1 && json_typeof (root) != typ) + { + (void) request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST, + 0, 0, + "{s:s, s:i, s:i s:o}", + "error", "wrong JSON field type", + "type_expected", typ, + "type_actual", json_typeof (root), + "path", path); + return GNUNET_NO; + } + *r_json = root; + return GNUNET_OK; + } + break; + default: + GNUNET_assert (0); + } + } + GNUNET_assert (0); +} + + + diff --git a/src/util/misc.supp b/src/util/misc.supp new file mode 100644 index 000000000..afcac6128 --- /dev/null +++ b/src/util/misc.supp @@ -0,0 +1,28 @@ +{ + + Memcheck:Leak + match-leak-kinds:reachable + ... + fun:GNUNET_CRYPTO_random_init + fun:call_init.part.0 + ... +} + +{ + + Memcheck:Leak + match-leak-kinds:reachable + ... + fun:point_from_keyparam + fun:_gcry_mpi_ec_new + ... +} + +{ + + Memcheck:Leak + match-leak-kinds:reachable + ... + fun:GNUNET_log_setup + ... +} \ No newline at end of file diff --git a/src/util/rsa.c b/src/util/rsa.c new file mode 100644 index 000000000..cde56be9e --- /dev/null +++ b/src/util/rsa.c @@ -0,0 +1,925 @@ +/* + 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 +*/ + +/** + * @file util/rsa.c + * @brief RSA key management utilities. Most of the code here is taken from + * gnunet-0.9.5a + * @author Sree Harsha Totakura + * + * Authors of the gnunet code: + * Christian Grothoff + * Krista Bennett + * Gerd Knorr + * Ioana Patrascu + * Tzvetan Horozov + */ + +#include "platform.h" +#include "gcrypt.h" +#include "gnunet/gnunet_util_lib.h" +#include "taler_rsa.h" + + + +#define LOG(kind,...) GNUNET_log_from (kind, "util", __VA_ARGS__) + +#define LOG_STRERROR(kind,syscall) GNUNET_log_from_strerror (kind, "util", syscall) + +#define LOG_STRERROR_FILE(kind,syscall,filename) GNUNET_log_from_strerror_file (kind, "util", syscall, filename) + +/** + * Log an error message at log-level 'level' that indicates + * a failure of the command 'cmd' with the message given + * by gcry_strerror(rc). + */ +#define LOG_GCRY(level, cmd, rc) do { LOG(level, _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, gcry_strerror(rc)); } while(0) + +/** + * Shorthand to cleanup non null mpi data types + */ +#define mpi_release_non_null(mpi) \ + if (NULL != mpi) gcry_mpi_release (mpi); + +/** + * The private information of an RSA key pair. + * NOTE: this must match the definition in crypto_ksk.c and gnunet-rsa.c! + */ +struct TALER_RSA_PrivateKey +{ + /** + * Libgcrypt S-expression for the ECC key. + */ + gcry_sexp_t sexp; +}; + + +/** + * Extract values from an S-expression. + * + * @param array where to store the result(s) + * @param sexp S-expression to parse + * @param topname top-level name in the S-expression that is of interest + * @param elems names of the elements to extract + * @return 0 on success + */ +static int +key_from_sexp (gcry_mpi_t * array, gcry_sexp_t sexp, const char *topname, + const char *elems) +{ + gcry_sexp_t list; + gcry_sexp_t l2; + const char *s; + unsigned int i; + unsigned int idx; + + if (! (list = gcry_sexp_find_token (sexp, topname, 0))) + return 1; + l2 = gcry_sexp_cadr (list); + gcry_sexp_release (list); + list = l2; + if (! list) + return 2; + idx = 0; + for (s = elems; *s; s++, idx++) + { + if (! (l2 = gcry_sexp_find_token (list, s, 1))) + { + for (i = 0; i < idx; i++) + { + gcry_free (array[i]); + array[i] = NULL; + } + gcry_sexp_release (list); + return 3; /* required parameter not found */ + } + array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG); + gcry_sexp_release (l2); + if (! array[idx]) + { + for (i = 0; i < idx; i++) + { + gcry_free (array[i]); + array[i] = NULL; + } + gcry_sexp_release (list); + return 4; /* required parameter is invalid */ + } + } + gcry_sexp_release (list); + return 0; +} + +/** + * If target != size, move target bytes to the + * end of the size-sized buffer and zero out the + * first target-size bytes. + * + * @param buf original buffer + * @param size number of bytes in the buffer + * @param target target size of the buffer + */ +static void +adjust (unsigned char *buf, size_t size, size_t target) +{ + if (size < target) + { + memmove (&buf[target - size], buf, size); + memset (buf, 0, target - size); + } +} + + +/** + * Create a new private key. Caller must free return value. + * + * @return fresh private key + */ +struct TALER_RSA_PrivateKey * +TALER_RSA_key_create () +{ + struct TALER_RSA_PrivateKey *ret; + gcry_sexp_t s_key; + gcry_sexp_t s_keyparam; + + GNUNET_assert (0 == + gcry_sexp_build (&s_keyparam, NULL, + "(genkey(rsa(nbits %d)(rsa-use-e 3:257)))", + 2048)); + GNUNET_assert (0 == gcry_pk_genkey (&s_key, s_keyparam)); + gcry_sexp_release (s_keyparam); +#if EXTRA_CHECKS + GNUNET_assert (0 == gcry_pk_testkey (s_key)); +#endif + ret = GNUNET_malloc (sizeof (struct TALER_RSA_PrivateKey)); + ret->sexp = s_key; + return ret; +} + + +/** + * Free memory occupied by the private key. + * + * @param key pointer to the memory to free + */ +void +TALER_RSA_key_free (struct TALER_RSA_PrivateKey *key) +{ + gcry_sexp_release (key->sexp); + GNUNET_free (key); +} + + +/** + * Encode the private key in a format suitable for + * storing it into a file. + * @return encoding of the private key + */ +struct TALER_RSA_PrivateKeyBinaryEncoded * +TALER_RSA_encode_key (const struct TALER_RSA_PrivateKey *hostkey) +{ + struct TALER_RSA_PrivateKeyBinaryEncoded *retval; + gcry_mpi_t pkv[6]; + void *pbu[6]; + size_t sizes[6]; + int rc; + int i; + int size; + +#if EXTRA_CHECKS + if (gcry_pk_testkey (hostkey->sexp)) + { + GNUNET_break (0); + return NULL; + } +#endif + + memset (pkv, 0, sizeof (gcry_mpi_t) * 6); + rc = key_from_sexp (pkv, hostkey->sexp, "private-key", "nedpqu"); + if (rc) + rc = key_from_sexp (pkv, hostkey->sexp, "rsa", "nedpqu"); + if (rc) + rc = key_from_sexp (pkv, hostkey->sexp, "private-key", "nedpq"); + if (rc) + rc = key_from_sexp (pkv, hostkey->sexp, "rsa", "nedpq"); + if (rc) + rc = key_from_sexp (pkv, hostkey->sexp, "private-key", "ned"); + if (rc) + rc = key_from_sexp (pkv, hostkey->sexp, "rsa", "ned"); + GNUNET_assert (0 == rc); + size = sizeof (struct TALER_RSA_PrivateKeyBinaryEncoded); + for (i = 0; i < 6; i++) + { + if (NULL != pkv[i]) + { + GNUNET_assert (0 == + gcry_mpi_aprint (GCRYMPI_FMT_USG, + (unsigned char **) &pbu[i], &sizes[i], + pkv[i])); + size += sizes[i]; + } + else + { + pbu[i] = NULL; + sizes[i] = 0; + } + } + GNUNET_assert (size < 65536); + retval = GNUNET_malloc (size); + retval->len = htons (size); + i = 0; + retval->sizen = htons (sizes[0]); + memcpy (&((char *) (&retval[1]))[i], pbu[0], sizes[0]); + i += sizes[0]; + retval->sizee = htons (sizes[1]); + memcpy (&((char *) (&retval[1]))[i], pbu[1], sizes[1]); + i += sizes[1]; + retval->sized = htons (sizes[2]); + memcpy (&((char *) (&retval[1]))[i], pbu[2], sizes[2]); + i += sizes[2]; + /* swap p and q! */ + retval->sizep = htons (sizes[4]); + memcpy (&((char *) (&retval[1]))[i], pbu[4], sizes[4]); + i += sizes[4]; + retval->sizeq = htons (sizes[3]); + memcpy (&((char *) (&retval[1]))[i], pbu[3], sizes[3]); + i += sizes[3]; + retval->sizedmp1 = htons (0); + retval->sizedmq1 = htons (0); + memcpy (&((char *) (&retval[1]))[i], pbu[5], sizes[5]); + for (i = 0; i < 6; i++) + { + if (pkv[i] != NULL) + gcry_mpi_release (pkv[i]); + if (pbu[i] != NULL) + free (pbu[i]); + } + return retval; +} + + +/** + * Extract the public key of the given private key. + * + * @param priv the private key + * @param pub where to write the public key + */ +void +TALER_RSA_key_get_public (const struct TALER_RSA_PrivateKey *priv, + struct TALER_RSA_PublicKeyBinaryEncoded *pub) +{ + gcry_mpi_t skey[2]; + size_t size; + int rc; + + rc = key_from_sexp (skey, priv->sexp, "public-key", "ne"); + if (0 != rc) + rc = key_from_sexp (skey, priv->sexp, "private-key", "ne"); + if (0 != rc) + rc = key_from_sexp (skey, priv->sexp, "rsa", "ne"); + GNUNET_assert (0 == rc); + pub->len = + htons (sizeof (struct TALER_RSA_PublicKeyBinaryEncoded) - + sizeof (pub->padding)); + pub->sizen = htons (TALER_RSA_DATA_ENCODING_LENGTH); + pub->padding = 0; + size = TALER_RSA_DATA_ENCODING_LENGTH; + GNUNET_assert (0 == + gcry_mpi_print (GCRYMPI_FMT_USG, &pub->key[0], size, &size, + skey[0])); + adjust (&pub->key[0], size, TALER_RSA_DATA_ENCODING_LENGTH); + size = TALER_RSA_KEY_LENGTH - TALER_RSA_DATA_ENCODING_LENGTH; + GNUNET_assert (0 == + gcry_mpi_print (GCRYMPI_FMT_USG, + &pub->key + [TALER_RSA_DATA_ENCODING_LENGTH], size, + &size, skey[1])); + adjust (&pub->key[TALER_RSA_DATA_ENCODING_LENGTH], size, + TALER_RSA_KEY_LENGTH - + TALER_RSA_DATA_ENCODING_LENGTH); + gcry_mpi_release (skey[0]); + gcry_mpi_release (skey[1]); +} + + +/** + * Decode the private key from the data-format back + * to the "normal", internal format. + * + * @param buf the buffer where the private key data is stored + * @param len the length of the data in 'buffer' + * @return NULL on error + */ +struct TALER_RSA_PrivateKey * +TALER_RSA_decode_key (const char *buf, uint16_t len) +{ + struct TALER_RSA_PrivateKey *ret; + const struct TALER_RSA_PrivateKeyBinaryEncoded *encoding = + (const struct TALER_RSA_PrivateKeyBinaryEncoded *) buf; + gcry_sexp_t res; + gcry_mpi_t n; + gcry_mpi_t e; + gcry_mpi_t d; + gcry_mpi_t p; + gcry_mpi_t q; + gcry_mpi_t u; + int rc; + size_t size; + size_t pos; + uint16_t enc_len; + size_t erroff; + + enc_len = ntohs (encoding->len); + if (len != enc_len) + return NULL; + + pos = 0; + size = ntohs (encoding->sizen); + rc = gcry_mpi_scan (&n, GCRYMPI_FMT_USG, + &((const unsigned char *) (&encoding[1]))[pos], size, + &size); + pos += ntohs (encoding->sizen); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + return NULL; + } + size = ntohs (encoding->sizee); + rc = gcry_mpi_scan (&e, GCRYMPI_FMT_USG, + &((const unsigned char *) (&encoding[1]))[pos], size, + &size); + pos += ntohs (encoding->sizee); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + gcry_mpi_release (n); + return NULL; + } + size = ntohs (encoding->sized); + rc = gcry_mpi_scan (&d, GCRYMPI_FMT_USG, + &((const unsigned char *) (&encoding[1]))[pos], size, + &size); + pos += ntohs (encoding->sized); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + gcry_mpi_release (n); + gcry_mpi_release (e); + return NULL; + } + /* swap p and q! */ + size = ntohs (encoding->sizep); + if (size > 0) + { + rc = gcry_mpi_scan (&q, GCRYMPI_FMT_USG, + &((const unsigned char *) (&encoding[1]))[pos], size, + &size); + pos += ntohs (encoding->sizep); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + gcry_mpi_release (n); + gcry_mpi_release (e); + gcry_mpi_release (d); + return NULL; + } + } + else + q = NULL; + size = ntohs (encoding->sizeq); + if (size > 0) + { + rc = gcry_mpi_scan (&p, GCRYMPI_FMT_USG, + &((const unsigned char *) (&encoding[1]))[pos], size, + &size); + pos += ntohs (encoding->sizeq); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + gcry_mpi_release (n); + gcry_mpi_release (e); + gcry_mpi_release (d); + if (NULL != q) + gcry_mpi_release (q); + return NULL; + } + } + else + p = NULL; + pos += ntohs (encoding->sizedmp1); + pos += ntohs (encoding->sizedmq1); + size = + ntohs (encoding->len) - sizeof (struct TALER_RSA_PrivateKeyBinaryEncoded) - pos; + if (size > 0) + { + rc = gcry_mpi_scan (&u, GCRYMPI_FMT_USG, + &((const unsigned char *) (&encoding[1]))[pos], size, + &size); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + gcry_mpi_release (n); + gcry_mpi_release (e); + gcry_mpi_release (d); + if (NULL != p) + gcry_mpi_release (p); + if (NULL != q) + gcry_mpi_release (q); + return NULL; + } + } + else + u = NULL; + + if ((NULL != p) && (NULL != q) && (NULL != u)) + { + rc = gcry_sexp_build (&res, &erroff, + "(private-key(rsa(n %m)(e %m)(d %m)(p %m)(q %m)(u %m)))", + n, e, d, p, q, u); + } + else + { + if ((NULL != p) && (NULL != q)) + { + rc = gcry_sexp_build (&res, &erroff, + "(private-key(rsa(n %m)(e %m)(d %m)(p %m)(q %m)))", + n, e, d, p, q); + } + else + { + rc = gcry_sexp_build (&res, &erroff, + "(private-key(rsa(n %m)(e %m)(d %m)))", n, e, d); + } + } + gcry_mpi_release (n); + gcry_mpi_release (e); + gcry_mpi_release (d); + if (NULL != p) + gcry_mpi_release (p); + if (NULL != q) + gcry_mpi_release (q); + if (NULL != u) + gcry_mpi_release (u); + + if (0 != rc) + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_sexp_build", rc); + if (0 != (rc = gcry_pk_testkey (res))) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_pk_testkey", rc); + return NULL; + } + ret = GNUNET_malloc (sizeof (struct TALER_RSA_PrivateKey)); + ret->sexp = res; + return ret; +} + + +/** + * Convert a public key to a string. + * + * @param pub key to convert + * @return string representing 'pub' + */ +char * +TALER_RSA_public_key_to_string (const struct TALER_RSA_PublicKeyBinaryEncoded *pub) +{ + char *pubkeybuf; + size_t keylen = (sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)) * 8; + char *end; + + if (keylen % 5 > 0) + keylen += 5 - keylen % 5; + keylen /= 5; + pubkeybuf = GNUNET_malloc (keylen + 1); + end = GNUNET_STRINGS_data_to_string ((unsigned char *) pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded), + pubkeybuf, + keylen); + if (NULL == end) + { + GNUNET_free (pubkeybuf); + return NULL; + } + *end = '\0'; + return pubkeybuf; +} + + +/** + * Convert a string representing a public key to a public key. + * + * @param enc encoded public key + * @param enclen number of bytes in enc (without 0-terminator) + * @param pub where to store the public key + * @return GNUNET_OK on success + */ +int +TALER_RSA_public_key_from_string (const char *enc, + size_t enclen, + struct TALER_RSA_PublicKeyBinaryEncoded *pub) +{ + size_t keylen = (sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)) * 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, + (unsigned char*) pub, + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded))) + return GNUNET_SYSERR; + if ( (ntohs (pub->len) != sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)) || + (ntohs (pub->padding) != 0) || + (ntohs (pub->sizen) != TALER_RSA_DATA_ENCODING_LENGTH) ) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +/** + * Convert the data specified in the given purpose argument to an + * S-expression suitable for signature operations. + * + * @param ptr pointer to the data to convert + * @param size the size of the data + * @return converted s-expression + */ +static gcry_sexp_t +data_to_sexp (const void *ptr, size_t size) +{ + gcry_mpi_t value; + gcry_sexp_t data; + + value = NULL; + data = NULL; + GNUNET_assert (0 == gcry_mpi_scan (&value, GCRYMPI_FMT_USG, ptr, size, NULL)); + GNUNET_assert (0 == gcry_sexp_build (&data, NULL, "(data (flags raw) (value %M))", value)); + gcry_mpi_release (value); + return data; +} + + +/** + * Sign the given hash block. + * + * @param key private key to use for the signing + * @param hash the block containing the hash of the message to sign + * @param hash_size the size of the hash block + * @param sig where to write the signature + * @return GNUNET_SYSERR on error, GNUNET_OK on success + */ +int +TALER_RSA_sign (const struct TALER_RSA_PrivateKey *key, + const void *hash, + size_t hash_size, + struct TALER_RSA_Signature *sig) +{ + gcry_sexp_t result; + gcry_sexp_t data; + size_t ssize; + gcry_mpi_t rval; + + data = data_to_sexp (hash, hash_size); + GNUNET_assert (0 == gcry_pk_sign (&result, data, key->sexp)); + gcry_sexp_release (data); + GNUNET_assert (0 == key_from_sexp (&rval, result, "rsa", "s")); + gcry_sexp_release (result); + ssize = sizeof (struct TALER_RSA_Signature); + GNUNET_assert (0 == + gcry_mpi_print (GCRYMPI_FMT_USG, (unsigned char *) sig, ssize, + &ssize, rval)); + gcry_mpi_release (rval); + adjust (sig->sig, ssize, sizeof (struct TALER_RSA_Signature)); + return GNUNET_OK; +} + + +/** + * Convert the given public key from the network format to the + * S-expression that can be used by libgcrypt. + * + * @param publicKey public key to decode + * @return NULL on error + */ +static gcry_sexp_t +decode_public_key (const struct TALER_RSA_PublicKeyBinaryEncoded *publicKey) +{ + gcry_sexp_t result; + gcry_mpi_t n; + gcry_mpi_t e; + size_t size; + size_t erroff; + int rc; + + if ((ntohs (publicKey->sizen) != TALER_RSA_DATA_ENCODING_LENGTH) || + (ntohs (publicKey->len) != + sizeof (struct TALER_RSA_PublicKeyBinaryEncoded) - + sizeof (publicKey->padding))) + { + GNUNET_break (0); + return NULL; + } + size = TALER_RSA_DATA_ENCODING_LENGTH; + if (0 != (rc = gcry_mpi_scan (&n, GCRYMPI_FMT_USG, &publicKey->key[0], size, &size))) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + return NULL; + } + size = TALER_RSA_KEY_LENGTH - TALER_RSA_DATA_ENCODING_LENGTH; + if (0 != (rc = gcry_mpi_scan (&e, GCRYMPI_FMT_USG, + &publicKey->key[TALER_RSA_DATA_ENCODING_LENGTH], + size, &size))) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + gcry_mpi_release (n); + return NULL; + } + rc = gcry_sexp_build (&result, &erroff, "(public-key(rsa(n %m)(e %m)))", n, + e); + gcry_mpi_release (n); + gcry_mpi_release (e); + if (0 != rc) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_sexp_build", rc); /* erroff gives more info */ + return NULL; + } + return result; +} + + +/** + * Verify signature with the given hash. + * + * @param hash the hash code to verify against the signature + * @param sig signature that is being validated + * @param publicKey public key of the signer + * @returns GNUNET_OK if ok, GNUNET_SYSERR if invalid + */ +int +TALER_RSA_hash_verify (const struct GNUNET_HashCode *hash, + const struct TALER_RSA_Signature *sig, + const struct TALER_RSA_PublicKeyBinaryEncoded *publicKey) +{ + gcry_sexp_t data; + gcry_sexp_t sigdata; + size_t size; + gcry_mpi_t val; + gcry_sexp_t psexp; + size_t erroff; + int rc; + + size = sizeof (struct TALER_RSA_Signature); + GNUNET_assert (0 == + gcry_mpi_scan (&val, GCRYMPI_FMT_USG, + (const unsigned char *) sig, size, &size)); + GNUNET_assert (0 == + gcry_sexp_build (&sigdata, &erroff, "(sig-val(rsa(s %m)))", + val)); + gcry_mpi_release (val); + data = data_to_sexp (hash, sizeof (struct GNUNET_HashCode)); + if (! (psexp = decode_public_key (publicKey))) + { + gcry_sexp_release (data); + gcry_sexp_release (sigdata); + return GNUNET_SYSERR; + } + rc = gcry_pk_verify (sigdata, data, psexp); + gcry_sexp_release (psexp); + gcry_sexp_release (data); + gcry_sexp_release (sigdata); + if (rc) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + _("RSA signature verification failed at %s:%d: %s\n"), __FILE__, + __LINE__, gcry_strerror (rc)); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Verify signature on the given message + * + * @param msg the message + * @param size the size of the message + * @param sig signature that is being validated + * @param publicKey public key of the signer + * @returns GNUNET_OK if ok, GNUNET_SYSERR if invalid + */ +int +TALER_RSA_verify (const void *msg, size_t size, + const struct TALER_RSA_Signature *sig, + const struct TALER_RSA_PublicKeyBinaryEncoded *publicKey) +{ + struct GNUNET_HashCode hash; + + GNUNET_CRYPTO_hash (msg, size, &hash); + return TALER_RSA_hash_verify (&hash, sig, publicKey); +} + +/** + * The blinding key is equal in length to the RSA modulus + */ +#define TALER_RSA_BLINDING_KEY_LEN TALER_RSA_DATA_ENCODING_LENGTH + +struct TALER_RSA_BlindingKey +{ + /** + * The blinding factor + */ + gcry_mpi_t r; +}; + +struct TALER_RSA_BlindingKey * +TALER_RSA_blinding_key_create () +{ + struct TALER_RSA_BlindingKey *blind; + + blind = GNUNET_new (struct TALER_RSA_BlindingKey); + blind->r = gcry_mpi_new (TALER_RSA_BLINDING_KEY_LEN * 8); + gcry_mpi_randomize (blind->r, TALER_RSA_BLINDING_KEY_LEN * 8, GCRY_STRONG_RANDOM); + return blind; +} + + +void +TALER_RSA_blinding_key_destroy (struct TALER_RSA_BlindingKey *bkey) +{ + gcry_mpi_release (bkey->r); + GNUNET_free (bkey); +} + + +struct TALER_RSA_BlindedSignaturePurpose * +TALER_RSA_message_blind (const void *msg, size_t size, + struct TALER_RSA_BlindingKey *bkey, + struct TALER_RSA_PublicKeyBinaryEncoded *pkey) +{ + struct TALER_RSA_BlindedSignaturePurpose *bsp; + struct GNUNET_HashCode hash; + gcry_sexp_t psexp; + gcry_mpi_t data; + gcry_mpi_t skey[2]; + gcry_mpi_t r_e; + gcry_mpi_t data_r_e; + size_t rsize; + gcry_error_t rc; + int ret; + + bsp = NULL; + psexp = NULL; + data = NULL; + skey[0] = skey[1] = NULL; + r_e = NULL; + data_r_e = NULL; + rsize = 0; + rc = 0; + ret = 0; + if (! (psexp = decode_public_key (pkey))) + return NULL; + ret = key_from_sexp (skey, psexp, "public-key", "ne"); + if (0 != ret) + ret = key_from_sexp (skey, psexp, "rsa", "ne"); + gcry_sexp_release (psexp); + psexp = NULL; + GNUNET_assert (0 == ret); + GNUNET_CRYPTO_hash (msg, size, &hash); + if (0 != (rc=gcry_mpi_scan (&data, GCRYMPI_FMT_USG, + (const unsigned char *) msg, size, &rsize))) + { + LOG_GCRY (GNUNET_ERROR_TYPE_WARNING, "gcry_mpi_scan", rc); + goto cleanup; + } + r_e = gcry_mpi_new (0); + gcry_mpi_powm (r_e, bkey->r, + skey[1], /* e */ + skey[0]); /* n */ + + data_r_e = gcry_mpi_new (0); + gcry_mpi_mulm (data_r_e, data, r_e, skey[0]); + + bsp = GNUNET_new (struct TALER_RSA_BlindedSignaturePurpose); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, + (unsigned char *) bsp, + sizeof (struct TALER_RSA_BlindedSignaturePurpose), + &rsize, + data_r_e); + GNUNET_assert (0 == rc); + adjust ((unsigned char *) bsp, rsize, + sizeof (struct TALER_RSA_BlindedSignaturePurpose)); + + cleanup: + if (NULL != psexp) gcry_sexp_release (psexp); + mpi_release_non_null (skey[0]); + mpi_release_non_null (skey[1]); + mpi_release_non_null (data); + mpi_release_non_null (r_e); + mpi_release_non_null (data_r_e); + return bsp; +} + + +int +TALER_RSA_unblind (struct TALER_RSA_Signature *sig, + struct TALER_RSA_BlindingKey *bkey, + struct TALER_RSA_PublicKeyBinaryEncoded *pkey) +{ + gcry_sexp_t psexp; + gcry_mpi_t skey; + gcry_mpi_t sigval; + gcry_mpi_t r_inv; + gcry_mpi_t ubsig; + size_t rsize; + gcry_error_t rc; + int ret; + + psexp = NULL; + skey = NULL; + sigval = NULL; + r_inv = NULL; + ubsig = NULL; + rsize = 0; + rc = 0; + ret = GNUNET_SYSERR; + if (! (psexp = decode_public_key (pkey))) + return GNUNET_SYSERR; + ret = key_from_sexp (&skey, psexp, "public-key", "n"); + if (0 != ret) + ret = key_from_sexp (&skey, psexp, "rsa", "n"); + gcry_sexp_release (psexp); + psexp = NULL; + if (0 != (rc = gcry_mpi_scan (&sigval, GCRYMPI_FMT_USG, + (const unsigned char *) sig, + sizeof (struct TALER_RSA_Signature), + &rsize))) + { + LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_mpi_scan", rc); + goto cleanup; + } + r_inv = gcry_mpi_new (0); + GNUNET_assert (1 == gcry_mpi_invm (r_inv, bkey->r, skey)); /* n: skey */ + ubsig = gcry_mpi_new (0); + gcry_mpi_mulm (ubsig, sigval, r_inv, skey); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, + (unsigned char *) sig, + sizeof (struct TALER_RSA_Signature), + &rsize, + ubsig); + GNUNET_assert (0 == rc); + adjust ((unsigned char *) sig, rsize, sizeof (struct TALER_RSA_Signature)); + ret = GNUNET_OK; + + cleanup: + if (NULL != psexp) gcry_sexp_release (psexp); + mpi_release_non_null (skey); + mpi_release_non_null (sigval); + mpi_release_non_null (r_inv); + mpi_release_non_null (ubsig); + return ret; +} + + +/** + * Encode a blinding key + * + * @param bkey the blinding key to encode + * @param bkey_enc where to store the encoded binary key + * @return #GNUNET_OK upon successful encoding; #GNUNET_SYSERR upon failure + */ +int +TALER_RSA_blinding_key_encode (struct TALER_RSA_BlindingKey *bkey, + struct TALER_RSA_BlindingKeyBinaryEncoded *bkey_enc) +{ + GNUNET_abort (); /* FIXME: not implemented */ +} + + +/** + * Decode a blinding key from its encoded form + * + * @param bkey_enc the encoded blinding key + * @return the decoded blinding key; NULL upon error + */ +struct TALER_RSA_BlindingKey * +TALER_RSA_blinding_key_decode (struct TALER_RSA_BlindingKeyBinaryEncoded *bkey_enc) +{ + GNUNET_abort (); /* FIXME: not implemented */ +} + +/* end of util/rsa.c */ diff --git a/src/util/test_hash_context.c b/src/util/test_hash_context.c new file mode 100644 index 000000000..e5110f212 --- /dev/null +++ b/src/util/test_hash_context.c @@ -0,0 +1,48 @@ +/* + 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 +*/ + +/** + * @file util/test_hash_context.c + * @brief test case for incremental hashing + * @author Florian Dold + */ + +#include "platform.h" +#include "taler_util.h" +#include + +#define LEN 1234 + +int main() +{ + char data[1234]; + struct GNUNET_HashCode hc1; + struct GNUNET_HashCode hc2; + struct TALER_HashContext hctx; + + memset (data, 42, LEN); + + TALER_hash_context_start (&hctx); + TALER_hash_context_read (&hctx, data, LEN); + TALER_hash_context_finish (&hctx, &hc1); + + GNUNET_CRYPTO_hash (data, LEN, &hc2); + + if (0 == memcmp (&hc1, &hc2, sizeof (struct GNUNET_HashCode))) + return 0; + return 1; +} + diff --git a/src/util/test_rsa.c b/src/util/test_rsa.c new file mode 100644 index 000000000..ac3ae2cd4 --- /dev/null +++ b/src/util/test_rsa.c @@ -0,0 +1,112 @@ +/* + 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 +*/ + +/** + * @file util/test_rsa.c + * @brief testcase for utility functions for RSA cryptography + * @author Sree Harsha Totakura + */ +#include "platform.h" +#include "taler_rsa.h" +#include + +#define TEST_PURPOSE UINT32_MAX + + +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + +int +main (int argc, char *argv[]) +{ +#define RND_BLK_SIZE 4096 + unsigned char rnd_blk[RND_BLK_SIZE]; + struct TALER_RSA_PrivateKey *priv; + struct TALER_RSA_PrivateKeyBinaryEncoded *priv_enc; + struct TALER_RSA_PublicKeyBinaryEncoded pubkey; + struct TALER_RSA_BlindingKey *bkey; + struct TALER_RSA_BlindedSignaturePurpose *bsp; + struct TALER_RSA_Signature sig; + struct GNUNET_HashCode hash; + int ret; + + priv = NULL; + bsp = NULL; + bkey = NULL; + ret = 1; + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, rnd_blk, + RND_BLK_SIZE); + GNUNET_CRYPTO_hash (rnd_blk, RND_BLK_SIZE, &hash); + priv = TALER_RSA_key_create (); + GNUNET_assert (NULL != priv); + EXITIF (GNUNET_OK != TALER_RSA_sign (priv, + &hash, sizeof (hash), + &sig)); + TALER_RSA_key_get_public (priv, &pubkey); + EXITIF (NULL == (priv_enc = TALER_RSA_encode_key (priv))); + TALER_RSA_key_free (priv); + priv = NULL; + EXITIF (NULL == (priv = TALER_RSA_decode_key ((const char *) priv_enc, + ntohs (priv_enc->len)))); + GNUNET_free (priv_enc); + priv_enc = NULL; + EXITIF (GNUNET_OK != TALER_RSA_hash_verify (&hash, + &sig, + &pubkey)); + EXITIF (GNUNET_OK != TALER_RSA_verify (rnd_blk, + RND_BLK_SIZE, + &sig, + &pubkey)); + + /* test blind signing */ + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, rnd_blk, + RND_BLK_SIZE); + GNUNET_CRYPTO_hash (rnd_blk, RND_BLK_SIZE, &hash); + (void) memset (&sig, 0, sizeof (struct TALER_RSA_Signature)); + EXITIF (NULL == (bkey = TALER_RSA_blinding_key_create ())); + EXITIF (NULL == (bsp = + TALER_RSA_message_blind (&hash, sizeof (hash), + bkey, &pubkey))); + EXITIF (GNUNET_OK != TALER_RSA_sign (priv, + bsp, + sizeof (struct TALER_RSA_BlindedSignaturePurpose), + &sig)); + EXITIF (GNUNET_OK != TALER_RSA_unblind (&sig, + bkey, + &pubkey)); + EXITIF (GNUNET_OK != TALER_RSA_hash_verify (&hash, + &sig, + &pubkey)); + ret = 0; /* all OK */ + + EXITIF_exit: + if (NULL != priv) + { + TALER_RSA_key_free (priv); + priv = NULL; + } + if (NULL != priv_enc) + { + GNUNET_free (priv_enc); + priv_enc = NULL; + } + if (NULL != bkey) + TALER_RSA_blinding_key_destroy (bkey); + GNUNET_free_non_null (bsp); + return ret; +} diff --git a/src/util/util.c b/src/util/util.c new file mode 100644 index 000000000..3677bcbde --- /dev/null +++ b/src/util/util.c @@ -0,0 +1,528 @@ +/* + 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 +*/ + +/** + * @file util.c + * @brief Common utility functions + * @author Sree Harsha Totakura + * @author Florian Dold + * @author Benedikt Mueller + */ + +#include "platform.h" +#include "taler_util.h" +#include +#include +#include + +#define CURVE "Ed25519" + +#define AMOUNT_FRAC_BASE 1000000 +#define AMOUNT_FRAC_LEN 6 + + + +static void +fatal_error_handler (void *cls, int wtf, const char *msg) +{ + LOG_ERROR("Fatal error in Gcrypt: %s\n", msg); + abort(); +} + + +/** + * Initialize Gcrypt library. + */ +void +TALER_gcrypt_init() +{ + gcry_set_fatalerror_handler (&fatal_error_handler, NULL); + TALER_assert_as(gcry_check_version(NEED_LIBGCRYPT_VERSION), + "libgcrypt version mismatch"); + /* Disable secure memory. */ + gcry_control (GCRYCTL_DISABLE_SECMEM, 0); + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); +} + + +/** + * Generate a ECC private key. + * + * @return the s-expression representing the generated ECC private key; NULL + * upon error + */ +gcry_sexp_t +TALER_genkey () +{ + gcry_sexp_t priv_sexp; + gcry_sexp_t s_keyparam; + int rc; + + if (0 != (rc = gcry_sexp_build (&s_keyparam, NULL, + "(genkey(ecc(curve \"" CURVE "\")" + "(flags eddsa)))"))) + { + LOG_GCRY_ERROR ("gcry_sexp_build", rc); + return NULL; + } + if (0 != (rc = gcry_pk_genkey (&priv_sexp, s_keyparam))) + { + LOG_GCRY_ERROR ("gcry_pk_genkey", rc); + gcry_sexp_release (s_keyparam); + return NULL; + } + gcry_sexp_release (s_keyparam); + if (0 != (rc = gcry_pk_testkey (priv_sexp))) + { + LOG_GCRY_ERROR("gcry_pk_testkey", rc); + gcry_sexp_release (priv_sexp); + return NULL; + } + return priv_sexp; +} + + +/** + * Parse money amount description, in the format "A:B.C". + * + * @param str amount description + * @param denom amount to write the result to + * @return GNUNET_OK if the string is a valid amount specification, + * GNUNET_SYSERR if it is invalid. + */ +int +TALER_string_to_amount (const char *str, struct TALER_Amount *denom) +{ + unsigned int i; // pos in str + int n; // number tmp + unsigned int c; // currency pos + uint32_t b; // base for suffix + + memset (denom, 0, sizeof (struct TALER_Amount)); + + i = n = c = 0; + + while (isspace(str[i])) + i++; + + if (0 == str[i]) + { + printf("null before currency\n"); + return GNUNET_SYSERR; + } + + while (str[i] != ':') + { + if (0 == str[i]) + { + printf("null before colon"); + return GNUNET_SYSERR; + } + if (c > 3) + { + printf("currency too long\n"); + return GNUNET_SYSERR; + } + denom->currency[c] = str[i]; + c++; + i++; + } + + // skip colon + i++; + + if (0 == str[i]) + { + printf("null before value\n"); + return GNUNET_SYSERR; + } + + while (str[i] != '.') + { + if (0 == str[i]) + { + return GNUNET_OK; + } + n = str[i] - '0'; + if (n < 0 || n > 9) + { + printf("invalid character '%c' before comma at %u\n", (char) n, i); + return GNUNET_SYSERR; + } + denom->value = (denom->value * 10) + n; + i++; + } + + // skip the dot + i++; + + if (0 == str[i]) + { + printf("null after dot"); + return GNUNET_SYSERR; + } + + b = 100000; + + while (0 != str[i]) + { + n = str[i] - '0'; + if (b == 0 || n < 0 || n > 9) + { + printf("error after comma"); + return GNUNET_SYSERR; + } + denom->fraction += n * b; + b /= 10; + i++; + } + + return GNUNET_OK; +} + + +/** + * FIXME + */ +struct TALER_AmountNBO +TALER_amount_hton (struct TALER_Amount d) +{ + struct TALER_AmountNBO dn; + dn.value = htonl (d.value); + dn.fraction = htonl (d.fraction); + memcpy (dn.currency, d.currency, TALER_CURRENCY_LEN); + + return dn; +} + + +/** + * FIXME + */ +struct TALER_Amount +TALER_amount_ntoh (struct TALER_AmountNBO dn) +{ + struct TALER_Amount d; + d.value = ntohl (dn.value); + d.fraction = ntohl (dn.fraction); + memcpy (d.currency, dn.currency, sizeof(dn.currency)); + + return d; +} + + +/** + * Compare the value/fraction of two amounts. Does not compare the currency, + * i.e. comparing amounts with the same value and fraction but different + * currency would return 0. + * + * @param a1 first amount + * @param a2 second amount + * @return result of the comparison + */ +int +TALER_amount_cmp (struct TALER_Amount a1, struct TALER_Amount a2) +{ + a1 = TALER_amount_normalize (a1); + a2 = TALER_amount_normalize (a2); + if (a1.value == a2.value) + { + if (a1.fraction < a2.fraction) + return -1; + if (a1.fraction > a2.fraction) + return 1; + return 0; + } + if (a1.value < a2.value) + return -1; + return 1; +} + + +/** + * Perform saturating subtraction of amounts. + * + * @param a1 amount to subtract from + * @param a2 amount to subtract + * @return (a1-a2) or 0 if a2>=a1 + */ +struct TALER_Amount +TALER_amount_subtract (struct TALER_Amount a1, struct TALER_Amount a2) +{ + a1 = TALER_amount_normalize (a1); + a2 = TALER_amount_normalize (a2); + + if (a1.value < a2.value) + { + a1.value = 0; + a1.fraction = 0; + return a1; + } + + if (a1.fraction < a2.fraction) + { + if (0 == a1.value) + { + a1.fraction = 0; + return a1; + } + a1.fraction += AMOUNT_FRAC_BASE; + a1.value -= 1; + } + + a1.fraction -= a2.fraction; + a1.value -= a2.value; + + return a1; +} + + +/** + * Perform saturating addition of amounts. + * + * @param a1 first amount to add + * @param a2 second amount to add + * @return sum of a1 and a2 + */ +struct TALER_Amount +TALER_amount_add (struct TALER_Amount a1, struct TALER_Amount a2) +{ + a1 = TALER_amount_normalize (a1); + a2 = TALER_amount_normalize (a2); + + a1.value += a2.value; + a1.fraction += a2.fraction; + + if (0 == a1.currency[0]) + { + memcpy (a2.currency, a1.currency, TALER_CURRENCY_LEN); + } + + if (0 == a2.currency[0]) + { + memcpy (a1.currency, a2.currency, TALER_CURRENCY_LEN); + } + + if (0 != a1.currency[0] && 0 != memcmp (a1.currency, a2.currency, TALER_CURRENCY_LEN)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "adding mismatching currencies\n"); + } + + if (a1.value < a2.value) + { + a1.value = UINT32_MAX; + a2.value = UINT32_MAX; + return a1; + } + + return TALER_amount_normalize (a1); +} + + +/** + * Normalize the given amount. + * + * @param amout amount to normalize + * @return normalized amount + */ +struct TALER_Amount +TALER_amount_normalize (struct TALER_Amount amount) +{ + while (amount.value != UINT32_MAX && amount.fraction >= AMOUNT_FRAC_BASE) + { + amount.fraction -= AMOUNT_FRAC_BASE; + amount.value += 1; + } + return amount; +} + + +/** + * Convert amount to string. + * + * @param amount amount to convert to string + * @return freshly allocated string representation + */ +char * +TALER_amount_to_string (struct TALER_Amount amount) +{ + char tail[AMOUNT_FRAC_LEN + 1] = { 0 }; + char curr[TALER_CURRENCY_LEN + 1] = { 0 }; + char *result = NULL; + int len; + + memcpy (curr, amount.currency, TALER_CURRENCY_LEN); + + amount = TALER_amount_normalize (amount); + if (0 != amount.fraction) + { + unsigned int i; + uint32_t n = amount.fraction; + for (i = 0; (i < AMOUNT_FRAC_LEN) && (n != 0); i++) + { + tail[i] = '0' + (n / (AMOUNT_FRAC_BASE / 10)); + n = (n * 10) % (AMOUNT_FRAC_BASE); + } + tail[i] = 0; + len = GNUNET_asprintf (&result, "%s:%lu.%s", curr, (unsigned long) amount.value, tail); + } + else + { + len = GNUNET_asprintf (&result, "%s:%lu", curr, (unsigned long) amount.value); + } + GNUNET_assert (len > 0); + return result; +} + + + +/** + * Return the base32crockford encoding of the given buffer. + * + * The returned string will be freshly allocated, and must be free'd + * with GNUNET_free. + * + * @param buffer with data + * @param size size of the buffer + * @return freshly allocated, null-terminated string + */ +char * +TALER_data_to_string_alloc (const void *buf, size_t size) +{ + char *str_buf; + size_t len = size * 8; + char *end; + + if (len % 5 > 0) + len += 5 - len % 5; + len /= 5; + str_buf = GNUNET_malloc (len + 1); + end = GNUNET_STRINGS_data_to_string (buf, size, str_buf, len); + if (NULL == end) + { + GNUNET_free (str_buf); + return NULL; + } + *end = '\0'; + return str_buf; +} + + +/** + * Get encoded binary data from a configuration. + * + * @return GNUNET_OK on success + * GNUNET_NO is the value does not exist + * GNUNET_SYSERR on encoding error + */ +int +TALER_configuration_get_data (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, const char *option, + void *buf, size_t buf_size) +{ + char *enc; + int res; + size_t data_size; + if (GNUNET_OK != (res = GNUNET_CONFIGURATION_get_value_string (cfg, section, option, &enc))) + return res; + data_size = (strlen (enc) * 5) / 8; + if (data_size != buf_size) + { + GNUNET_free (enc); + return GNUNET_SYSERR; + } + if (GNUNET_OK != GNUNET_STRINGS_string_to_data (enc, strlen (enc), + buf, buf_size)) + { + GNUNET_free (enc); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +static void +derive_refresh_key (const struct GNUNET_HashCode *secret, + struct GNUNET_CRYPTO_SymmetricInitializationVector *iv, + struct GNUNET_CRYPTO_SymmetricSessionKey *skey) +{ + static const char ctx_key[] = "taler-key-skey"; + static const char ctx_iv[] = "taler-key-iv"; + + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (skey, sizeof (struct GNUNET_CRYPTO_SymmetricSessionKey), + ctx_key, strlen (ctx_key), + secret, sizeof (struct GNUNET_HashCode), + NULL, 0)); + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (iv, sizeof (struct GNUNET_CRYPTO_SymmetricInitializationVector), + ctx_iv, strlen (ctx_iv), + secret, sizeof (struct GNUNET_HashCode), + NULL, 0)); +} + + +int +TALER_refresh_decrypt (const void *input, size_t input_size, const struct GNUNET_HashCode *secret, void *result) +{ + struct GNUNET_CRYPTO_SymmetricInitializationVector iv; + struct GNUNET_CRYPTO_SymmetricSessionKey skey; + + derive_refresh_key (secret, &iv, &skey); + + return GNUNET_CRYPTO_symmetric_decrypt (input, input_size, &skey, &iv, result); +} + + +int +TALER_refresh_encrypt (const void *input, size_t input_size, const struct GNUNET_HashCode *secret, void *result) +{ + struct GNUNET_CRYPTO_SymmetricInitializationVector iv; + struct GNUNET_CRYPTO_SymmetricSessionKey skey; + + derive_refresh_key (secret, &iv, &skey); + + return GNUNET_CRYPTO_symmetric_encrypt (input, input_size, &skey, &iv, result); +} + + +void +TALER_hash_context_start (struct TALER_HashContext *hc) +{ + GNUNET_assert (0 == gcry_md_open (&hc->hd, GCRY_MD_SHA512, 0)); +} + + +void +TALER_hash_context_read (struct TALER_HashContext *hc, void *buf, size_t size) +{ + gcry_md_write (hc->hd, buf, size); +} + + +void +TALER_hash_context_finish (struct TALER_HashContext *hc, + struct GNUNET_HashCode *r_hash) +{ + void *res = gcry_md_read (hc->hd, 0); + GNUNET_assert (NULL != res); + if (NULL != r_hash) + memcpy (r_hash, res, sizeof (struct GNUNET_HashCode)); + gcry_md_close (hc->hd); +} + + +/* end of util.c */ -- cgit v1.2.3