diff options
34 files changed, 11584 insertions, 261 deletions
diff --git a/configure.ac b/configure.ac index 2792558f..400045be 100644 --- a/configure.ac +++ b/configure.ac @@ -177,5 +177,7 @@ AC_TYPE_UINTMAX_T # Checks for library functions. AC_CHECK_FUNCS([strdup]) -AC_CONFIG_FILES([src/backend/Makefile]) +AC_CONFIG_FILES([Makefile +src/Makefile +src/backend/Makefile]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index 0bd932c0..46fa8eba 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,2 +1,2 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/include -SUBDIRS = include merchant +SUBDIRS = include backend diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index f7afde8e..f068f774 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -1,24 +1,32 @@ -AM_CPPFLAGS = -I$(top_srcdir)/src/include $(POSTGRESQL_CPPFLAGS) +# This Makefile.am is in the public domain +# AM_CPPFLAGS = -I$(top_srcdir)/src/include -MERCHANT_DB = merchant_db.c merchant_db.h bin_PROGRAMS = \ - taler-merchant-httpd \ - taler-merchant-dbinit + taler-merchant-httpd taler_merchant_httpd_SOURCES = \ - taler-merchant-httpd.c - -taler_merchant_dbinit_SOURCES = \ - taler-merchant-dbinit.c \ - $(MERCHANT_DB) + taler-merchant-httpd.c \ + merchant.c merchant.h \ + merchant_db.c merchant_db.h \ + taler-mint-httpd_keystate.c taler-mint-httpd_keystate.h \ + taler-mint-httpd_db.c taler-mint-httpd_db.h \ + taler-mint-httpd_parsing.c taler-mint-httpd_parsing.h \ + taler-mint-httpd_responses.c taler-mint-httpd_responses.h \ + taler-mint-httpd_mhd.c taler-mint-httpd_mhd.h \ + taler-mint-httpd_admin.c taler-mint-httpd_admin.h \ + taler-mint-httpd_deposit.c taler-mint-httpd_deposit.h \ + taler-mint-httpd_withdraw.c taler-mint-httpd_withdraw.h \ + taler-mint-httpd_refresh.c taler-mint-httpd_refresh.h taler_merchant_httpd_LDADD = \ + $(LIBGCRYPT_LIBS) \ + /home/marcello/trans_mint/src/util/libtalerutil.la \ + /home/marcello/trans_mint/src/mintdb/libtalermintdb.la \ -lmicrohttpd \ - -lgnunetutil - - -taler_merchant_dbinit_LDADD = \ - -ltalerpq \ + -ljansson \ -lgnunetutil \ + -ltalermint \ + -ltalerpq \ -lgnunetpostgres \ - -lpq + -lpq \ + -lpthread diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf new file mode 100644 index 00000000..3b637448 --- /dev/null +++ b/src/backend/merchant.conf @@ -0,0 +1,18 @@ +[merchant] +PORT = 9966 +HOSTNAME = localhost +TRUSTED_MINTS = taler +KEYFILE = merchant.priv + +[mint-taler] +HOSTNAME = demo.taler.net +PORT = 80 +PUBKEY = Q1WVGRGC1F4W7RYC6M23AEGFEXQEHQ730K3GG0B67VPHQSRR75H0 + +[merchant-db] +CONFIG = postgres:///taler + +[wire-sepa] +IBAN = DE67830654080004822650 +NAME = GNUNET E.V +BIC = GENODEF1SRL diff --git a/src/backend/merchant_db.c b/src/backend/merchant_db.c index 66ab5bcf..274de25a 100644 --- a/src/backend/merchant_db.c +++ b/src/backend/merchant_db.c @@ -87,23 +87,24 @@ MERCHANT_DB_initialize (PGconn *conn, int tmp) (void) GNUNET_asprintf (&sql, "BEGIN TRANSACTION;" "CREATE %1$s TABLE IF NOT EXISTS contracts (" - "transaction_id SERIAL8 PRIMARY KEY," - "amount INT4 NOT NULL," + "contract_id INT8 PRIMARY KEY," + "amount INT8 NOT NULL," "amount_fraction INT4 NOT NULL," + "amount_currency VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL," "description TEXT NOT NULL," - "nounce BYTEA NOT NULL," + "nounce INT8 NOT NULL," "expiry INT8 NOT NULL," "product INT8 NOT NULL);" "CREATE %1$s TABLE IF NOT EXISTS checkouts (" "coin_pub BYTEA PRIMARY KEY," - "transaction_id INT8 REFERENCES contracts(transaction_id)," + "contract_id INT8 REFERENCES contracts(contract_id)," "amount INT4 NOT NULL," "amount_fraction INT4 NOT NULL," "coin_sig BYTEA NOT NULL);", tmp_str); ret = GNUNET_POSTGRES_exec (conn, sql); (void) GNUNET_POSTGRES_exec (conn, - (GNUNET_OK == ret) ? "COMMIT;" : "ROLLBACK"); + (GNUNET_OK == ret) ? "COMMIT;" : "ROLLBACK;"); GNUNET_free (sql); if (GNUNET_OK != ret) return ret; @@ -118,11 +119,10 @@ MERCHANT_DB_initialize (PGconn *conn, int tmp) (conn, "contract_create", "INSERT INTO contracts" - "(amount, amount_fraction, description," - "nounce, expiry, product) VALUES" - "($1, $2, $3, $4, $5, $6)" - "RETURNING transaction_id", - 6, NULL))); + "(contract_id, amount, amount_fraction, amount_currency," + "description, nounce, expiry, product) VALUES" + "($1, $2, $3, $4, $5, $6, $7, $8)", + 8, NULL))); EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus(res))); PQclear (res); @@ -133,7 +133,7 @@ MERCHANT_DB_initialize (PGconn *conn, int tmp) "product" ") FROM contracts " "WHERE (" - "transaction_id=$1" + "contract_id=$1" ")", 1, NULL))); EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus(res))); @@ -144,7 +144,7 @@ MERCHANT_DB_initialize (PGconn *conn, int tmp) "checkout_create", "INSERT INTO checkouts (" "coin_pub," - "transaction_id," + "contract_id," "amount," "amount_fraction," "coin_sig" @@ -162,8 +162,8 @@ MERCHANT_DB_initialize (PGconn *conn, int tmp) "product" ") FROM contracts " "WHERE " - "transaction_id IN (" - "SELECT (transaction_id) FROM checkouts " + "contract_id IN (" + "SELECT (contract_id) FROM checkouts " "WHERE coin_pub=$1" ")", 1, NULL))); @@ -189,61 +189,67 @@ MERCHANT_DB_initialize (PGconn *conn, int tmp) * @param conn the database connection * @param expiry the time when the contract will expire * @param amount the taler amount corresponding to the contract + * @param c_id contract's id * @param desc descripition of the contract * @param nounce a random 64-bit nounce * @param product description to identify a product - * @return -1 upon error; the serial id of the inserted contract upon success + * @return GNUNET_OK on success, GNUNET_SYSERR upon error */ -long long + +uint32_t MERCHANT_DB_contract_create (PGconn *conn, - struct GNUNET_TIME_Absolute expiry, - struct TALER_Amount *amount, + const struct GNUNET_TIME_Absolute *expiry, + const struct TALER_Amount *amount, + uint64_t c_id, const char *desc, uint64_t nounce, uint64_t product) { PGresult *res; + #if 0 uint64_t expiry_ms_nbo; - uint32_t value_nbo; + uint64_t value_nbo; uint32_t fraction_nbo; uint64_t nounce_nbo; + #endif ExecStatusType status; - uint64_t id; + + #if 0 + /* + NOTE: the conversion to nl(l) happens *inside* the query param helpers; since + the policy imposes this format for storing values. */ + value_nbo = GNUNET_htonll (amount->value); + fraction_nbo = GNUNET_htonll (amount->fraction); + nounce_nbo = GNUNET_htonll (nounce); + expiry_ms_nbo = GNUNET_htonll (expiry.abs_value_us); + product = GNUNET_htonll (product); + #endif + /* ported. To be tested/compiled */ struct TALER_PQ_QueryParam params[] = { - TALER_PQ_query_param_uint32 (&value_nbo), - TALER_PQ_query_param_uint32 (&fraction_nbo), + TALER_PQ_query_param_uint64 (&c_id), + TALER_PQ_query_param_amount (amount), /* a *string* is being put in the following statement, though the API talks about a *blob*. Will this be liked by the DB ? */ + // the following inserts a string as a blob. Will Taler provide a param-from-string helper? TALER_PQ_query_param_fixed_size (desc, strlen(desc)), - TALER_PQ_query_param_uint64 (&nounce_nbo), - TALER_PQ_query_param_uint64 (&expiry_ms_nbo), + TALER_PQ_query_param_uint64 (&nounce), + TALER_PQ_query_param_absolute_time (expiry), TALER_PQ_query_param_uint64 (&product), TALER_PQ_query_param_end }; - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_result_spec_uint64 ("transaction_id", &id), - TALER_PQ_result_spec_end - }; - - expiry_ms_nbo = GNUNET_htonll (expiry.abs_value_us); - value_nbo = htonl (amount->value); - fraction_nbo = htonl (amount->fraction); - nounce_nbo = GNUNET_htonll (nounce); - product = GNUNET_htonll (product); + /* NOTE: the statement is prepared by MERCHANT_DB_initialize function */ res = TALER_PQ_exec_prepared (conn, "contract_create", params); status = PQresultStatus (res); - EXITIF (PGRES_TUPLES_OK != status); - EXITIF (1 != PQntuples (res)); - EXITIF (GNUNET_YES != TALER_PQ_extract_result (res, rs, 0)); + EXITIF (PGRES_COMMAND_OK != status); PQclear (res); - return GNUNET_ntohll ((uint64_t) id); + return GNUNET_OK; EXITIF_exit: PQclear (res); - return -1; + return GNUNET_SYSERR; } long long diff --git a/src/backend/merchant_db.h b/src/backend/merchant_db.h index bf334989..a723b229 100644 --- a/src/backend/merchant_db.h +++ b/src/backend/merchant_db.h @@ -64,15 +64,18 @@ MERCHANT_DB_initialize (PGconn *conn, int tmp); * @param conn the database connection * @param expiry the time when the contract will expire * @param amount the taler amount corresponding to the contract + * @param c_id this contract's identification number * @param desc descripition of the contract * @param nounce a random 64-bit nounce * @param product description to identify a product - * @return -1 upon error; the serial id of the inserted contract upon success + * @return GNUNET_OK on success, GNUNET_SYSERR upon error */ -long long + +uint32_t MERCHANT_DB_contract_create (PGconn *conn, - struct GNUNET_TIME_Absolute expiry, - struct TALER_Amount *amount, + const struct GNUNET_TIME_Absolute *expiry, + const struct TALER_Amount *amount, + uint64_t c_id, const char *desc, uint64_t nounce, uint64_t product); diff --git a/src/backend/myconf.sh b/src/backend/myconf.sh new file mode 100644 index 00000000..0b7d2594 --- /dev/null +++ b/src/backend/myconf.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +./configure CFLAGS='-I/usr/include/postgresql' diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index 6ca70937..46379809 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -22,9 +22,23 @@ #include "platform.h" #include <microhttpd.h> +#include <jansson.h> #include <gnunet/gnunet_util_lib.h> #include <taler/taler_json_lib.h> - +#include <taler/taler_mint_service.h> +#include "taler-mint-httpd_parsing.h" +#include "taler-mint-httpd_mhd.h" +#include "taler-mint-httpd_admin.h" +#include "taler-mint-httpd_deposit.h" +#include "taler-mint-httpd_withdraw.h" +#include "taler-mint-httpd_refresh.h" +#include "taler-mint-httpd_keystate.h" +#include "taler-mint-httpd_responses.h" +#include "merchant.h" +#include "merchant_db.h" + +extern struct MERCHANT_WIREFORMAT_Sepa * +TALER_MERCHANT_parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg); /** * Shorthand for exit jumps. @@ -34,232 +48,168 @@ if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ } while (0) -// task 1. Just implement a hello world server launched a` la GNUNET - /** - * The port we are running on + * Macro to round microseconds to seconds in GNUNET_TIME_* structs. */ -unsigned short port; +#define ROUND_TO_SECS(name,us_field) name.us_field -= name.us_field % (1000 * 1000) /** - * The MHD Daemon + * Our hostname */ -static struct MHD_Daemon *mhd; +static char *hostname; /** - * Shutdown task identifier + * The port we are running on */ -static struct GNUNET_SCHEDULER_Task *shutdown_task; +static long long unsigned port; /** - * Should we do a dry run where temporary tables are used for storing the data. + * Merchant's private key */ -static int dry; +struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; /** - * Global return code + * The MHD Daemon */ -static int result; - -/** Beginning of JSON parse logic -* Located here only for testing purposes since the service they provide is already -* implemented in the mint's code; it just needs to be exported as a library. To be announced as a issue. -*/ +static struct MHD_Daemon *mhd; /** - * Initial size for POST - * request buffer. + * Connection handle to the our database */ -#define REQUEST_BUFFER_INITIAL 1024 +PGconn *db_conn; /** - * Maximum POST request size + * Which currency is used by this mint? + * (verbatim copy from mint's code, just to make this + * merchant's source compile) */ -#define REQUEST_BUFFER_MAX (1024*1024) +char *TMH_mint_currency_string; + +/* As above */ +struct TALER_MINTDB_Plugin *TMH_plugin; /** - * Buffer for POST requests. + * As above, though the merchant does need some form of + * configuration */ -struct Buffer -{ - /** - * Allocated memory - */ - char *data; +struct GNUNET_CONFIGURATION_Handle *cfg; - /** - * Number of valid bytes in buffer. - */ - size_t fill; - /** - * Number of allocated bytes in buffer. - */ - size_t alloc; -}; +/** + * As above + */ +int TMH_test_mode; /** - * 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 + * As above */ -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; -} +char *TMH_mint_directory; /** - * Free the data in a buffer. Does *not* free - * the buffer object itself. - * - * @param buf buffer to de-initialize + * As above */ -static void -buffer_deinit (struct Buffer *buf) -{ - GNUNET_free (buf->data); - buf->data = NULL; -} +struct GNUNET_CRYPTO_EddsaPublicKey TMH_master_public_key; +/** + * As above + */ +char *TMH_expected_wire_format; /** - * 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?) + * Shutdown task identifier */ -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; -} +static struct GNUNET_SCHEDULER_Task *shutdown_task; +/** + * Our wireformat + */ +static struct MERCHANT_WIREFORMAT_Sepa *wire; +/** + * Hash of the wireformat + */ +static struct GNUNET_HashCode h_wire; /** - * 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 + * Should we do a dry run where temporary tables are used for storing the data. */ -static int -process_post_json (struct MHD_Connection *connection, - void **con_cls, - const char *upload_data, - size_t *upload_data_size, - json_t **json) +static int dry; + +/** + * Global return code + */ +static int result; + +GNUNET_NETWORK_STRUCT_BEGIN + +struct Contract { - struct Buffer *r = *con_cls; + /** + * The signature of the merchant for this contract + */ + struct GNUNET_CRYPTO_EddsaSignature sig; - if (NULL == *con_cls) - { - /* We are seeing a fresh POST request. */ + /** + * Purpose header for the signature over contract + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; - 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. */ + /** + * The transaction identifier + */ + char m[13]; - if (GNUNET_OK != buffer_append (r, upload_data, *upload_data_size, - REQUEST_BUFFER_MAX)) - { - /* Request too long or we're out of memory. */ + /** + * Expiry time + */ + struct GNUNET_TIME_AbsoluteNBO t; - *con_cls = NULL; - buffer_deinit (r); - GNUNET_free (r); - return GNUNET_SYSERR; - } - *upload_data_size = 0; - return GNUNET_NO; - } + /** + * The invoice amount + */ + struct TALER_AmountNBO amount; - /* We have seen the whole request. */ + /** + * The hash of the preferred wire format + nounce + */ + struct GNUNET_HashCode h_wire; - *json = json_loadb (r->data, r->fill, 0, NULL); - buffer_deinit (r); - GNUNET_free (r); - if (NULL == *json) - { - struct MHD_Response *resp; - int ret; - - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Can't parse JSON request body\n"); - resp = MHD_create_response_from_buffer (strlen ("parse error"), - "parse error", - MHD_RESPMEM_PERSISTENT); - ret = MHD_queue_response (connection, - MHD_HTTP_BAD_REQUEST, - resp); - MHD_destroy_response (resp); - return ret; - } - *con_cls = NULL; + /** + * The contract data + */ + char a[]; +}; - return GNUNET_YES; -} +GNUNET_NETWORK_STRUCT_END +/** + * Mint context + */ +static struct TALER_MINT_Context *mctx; -/* ************** END of JSON POST processing logic ************ */ +/** + * Context information of the mints we trust + */ +struct Mint +{ + /** + * Public key of this mint + */ + struct GNUNET_CRYPTO_EddsaPublicKey pubkey; + /** + * Connection handle to this mint + */ + struct TALER_MINT_Handle *conn; +}; +/** + * Hashmap to store the mint context information + */ +static struct GNUNET_CONTAINER_MultiPeerMap *mints_map; /** * Return the given message to the other end of connection @@ -296,7 +246,7 @@ static unsigned int generate_hello (struct MHD_Response **resp) // this parameter was preceded by a '_' in its original file. Why? { - const char *hello = "Hello customer"; + const char *hello = "Hello customer\n"; unsigned int ret; *resp = MHD_create_response_from_buffer (strlen (hello), (void *) hello, @@ -307,6 +257,26 @@ generate_hello (struct MHD_Response **resp) // this parameter was preceded by a } +/** + * Callback for catching serious error conditions from MHD. + * + * @param cls user specified value + * @param file where the error occured + * @param line where the error occured + * @param reason error detail, may be NULL + */ +static void +mhd_panic_cb (void *cls, + const char *file, + unsigned int line, + const char *reason) +{ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "MHD panicked at %s:%u: %s", + file, line, reason); + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); +} /** * Manage a non 200 HTTP status. I.e. it shows a 'failure' page to @@ -320,7 +290,6 @@ generate_hello (struct MHD_Response **resp) // this parameter was preceded by a static int failure_resp (struct MHD_Connection *connection, unsigned int status) { - printf ("called failure mgmt\n"); static char page_404[]="\ <!DOCTYPE html> \ <html><title>Resource not found</title><body><center> \ @@ -365,6 +334,91 @@ request</h3></center></body></html>"; /** +* Generate the hash containing the information (= a nounce + merchant's IBAN) to +* redeem money from mint in a subsequent /deposit operation +* @param nounce the nounce +* @return the hash to be included in the contract's blob +* +*/ + +static struct GNUNET_HashCode +hash_wireformat (uint64_t nounce) +{ + struct GNUNET_HashContext *hc; + struct GNUNET_HashCode hash; + + hc = GNUNET_CRYPTO_hash_context_start (); + GNUNET_CRYPTO_hash_context_read (hc, wire->iban, strlen (wire->iban)); + GNUNET_CRYPTO_hash_context_read (hc, wire->name, strlen (wire->name)); + GNUNET_CRYPTO_hash_context_read (hc, wire->bic, strlen (wire->bic)); + nounce = GNUNET_htonll (nounce); + GNUNET_CRYPTO_hash_context_read (hc, &nounce, sizeof (nounce)); + GNUNET_CRYPTO_hash_context_finish (hc, &hash); + return hash; +} + + + +/* +* Make a binary blob representing a contract, store it into the DB, sign it +* and return a pointer to it. +* @param a 0-terminated string representing the description of this +* @param c_id contract id provided by the frontend +* purchase (it should contain a human readable description of the good +* in question) +* @param product some product numerical id. Its indended use is to link the +* good, or service being sold to some entry in the DB managed by the frontend +* @price the cost of this good or service +* @return pointer to the allocated contract (which has a field, 'sig', holding +* its own signature), NULL upon errors +*/ + +struct Contract * +generate_and_store_contract (const char *a, uint64_t c_id, uint64_t product, struct TALER_Amount *price) +{ + + struct Contract *contract; + struct GNUNET_TIME_Absolute expiry; + uint64_t nounce; + uint64_t contract_id_nbo; + + expiry = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), + GNUNET_TIME_UNIT_DAYS); + ROUND_TO_SECS (expiry, abs_value_us); + nounce = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); + EXITIF (GNUNET_SYSERR == MERCHANT_DB_contract_create (db_conn, + &expiry, + price, + c_id, + a, + nounce, + product)); + contract_id_nbo = GNUNET_htonll ((uint64_t) c_id); + contract = GNUNET_malloc (sizeof (struct Contract) + strlen (a) + 1); + contract->purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT); + contract->purpose.size = htonl (sizeof (struct Contract) + - offsetof (struct Contract, purpose) + + strlen (a) + 1); + GNUNET_STRINGS_data_to_string (&contract_id_nbo, sizeof (contract_id_nbo), + contract->m, sizeof (contract->m)); + contract->t = GNUNET_TIME_absolute_hton (expiry); + (void) strcpy (contract->a, a); + contract->h_wire = hash_wireformat (nounce); + TALER_amount_hton (&contract->amount, price); + GNUNET_CRYPTO_eddsa_sign (privkey, &contract->purpose, &contract->sig); + return contract; + + /* legacy from old merchant */ + EXITIF_exit: + if (NULL != contract) + { + GNUNET_free (contract); + } + return NULL; +} + + +/** * A client has requested the given url using the given method * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback @@ -412,19 +466,32 @@ url_handler (void *cls, const char *version, const char *upload_data, size_t *upload_data_size, - void **con_cls) + void **connection_cls) { unsigned int status; unsigned int no_destroy; + json_int_t prod_id; + json_int_t contract_id; + struct Contract *contract; struct MHD_Response *resp; - + struct TALER_Amount price; + struct GNUNET_CRYPTO_EddsaPublicKey pub; + json_t *json_price; + json_t *root; + json_t *contract_enc; + json_t *sig_enc; + json_t *eddsa_pub_enc; + json_t *response; + + int res; + const char *desc; #define URL_HELLO "/hello" #define URL_CONTRACT "/contract" no_destroy = 0; resp = NULL; - status = 500; + status = MHD_HTTP_INTERNAL_SERVER_ERROR; if (0 == strncasecmp (url, URL_HELLO, sizeof (URL_HELLO))) { if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) @@ -436,24 +503,134 @@ url_handler (void *cls, // to be called by the frontend passing all the product's information // which are relevant for the contract's generation if (0 == strncasecmp (url, URL_CONTRACT, sizeof (URL_CONTRACT))) + { + if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) + status = generate_message (&resp, "Sorry, only POST is allowed"); + else + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + + if (GNUNET_SYSERR == res) { - if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) - status = generate_message (&resp, "Sorry, only POST is allowed"); - else - - /* - 1. parse the json - 2. generate the contract - 3. pack the contract's json - 4. return it - */ - - GNUNET_break (0); - + status = generate_message (&resp, "unable to parse JSON root"); + return MHD_NO; } + if ((GNUNET_NO == res) || (NULL == root)) + return MHD_YES; + + /* The frontend should supply a JSON in the follwoing format: + { + + "desc" : string human-readable describing this deal, + "product" : uint64-like integer referring to the product in some + DB adminstered by the frontend, + "cid" : uint64-like integer, this contract's id + "price" : a 'struct TALER_Amount' in the Taler compliant JSON format } + + */ + + #if 0 + /*res = json_typeof (root); <- seg fault*/ + json_int_t id; + const char *desc_test; + const char *cur_test; + json_t *id_json; + json_t *desc_json; + json_t *cur_json; + id_json = json_object_get (root, "product"); + desc_json = json_object_get (root, "desc"); + id = json_integer_value (id_json); + desc_test = json_string_value (desc_json); + json_price = json_object_get (root, "price"); + json_typeof (json_price); + cur_json = json_object_get (json_price, "currency"); + cur_test = json_string_value (cur_json); + printf ("id is %" JSON_INTEGER_FORMAT "\n", id); + printf ("desc is %s\n", desc_test); + TALER_json_to_amount (json_price, &price); + printf ("cur_test is %s\n", price.currency); + json_error_t err; + if (res = json_unpack_ex (root, &err, JSON_VALIDATE_ONLY, "{s:s, s:I, s:o}", + "desc", + //&desc, + "product", + //&prod_id, + "price"//, + //json_price + )) + #else + if ((res = json_unpack (root, "{s:s, s:I, s:I, s:o}", + "desc", + &desc, + "product", + &prod_id, + "cid", + &contract_id, + "price", + &json_price + ))) + #endif + + + /* still not possible to return a taler-compliant error message + since this JSON format is not among the taler officials ones */ + { + status = generate_message (&resp, "unable to parse /contract JSON\n"); + } + else + { + if (GNUNET_OK != TALER_json_to_amount (json_price, &price)) + {/* still not possible to return a taler-compliant error message + since this JSON format is not among the taler officials ones */ + status = generate_message (&resp, "unable to parse `price' field in /contract JSON");} + else + { + /* Let's generate this contract! */ + if (NULL == (contract = generate_and_store_contract (desc, contract_id, prod_id, &price))) + { + /* status equals 500, so the user will get a "Internal server error" */ + //failure_resp (connection, status); + status = generate_message (&resp, "unable to generate and store this contract"); + //return MHD_YES; + + } + else + { + json_decref (root); + json_decref (json_price); + + printf ("Good contract\n"); + /* the contract is good and stored in DB, produce now JSON to return. + As of now, the format is {"contract" : base32contract, + "sig" : contractSignature, + "eddsa_pub" : keyToCheckSignatureAgainst + } + + */ + + sig_enc = TALER_json_from_eddsa_sig (&contract->purpose, &contract->sig); + GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pub); + eddsa_pub_enc = TALER_json_from_data ((void *) &pub, sizeof (pub)); + /* cutting of the signature at the beginning */ + contract_enc = TALER_json_from_data (&contract->purpose, sizeof (*contract) + - offsetof (struct Contract, purpose) + + strlen (desc) +1); + response = json_pack ("{s:o, s:o, s:o}", "contract", contract_enc, "sig", sig_enc, + "eddsa_pub", eddsa_pub_enc); + TMH_RESPONSE_reply_json (connection, response, MHD_HTTP_OK); + printf ("Got something?\n"); + return MHD_YES; + /* FRONTIER - CODE ABOVE STILL NOT TESTED */ + } + } + } + } if (NULL != resp) { @@ -461,15 +638,14 @@ url_handler (void *cls, if (!no_destroy) MHD_destroy_response (resp); } - else - EXITIF (GNUNET_OK != failure_resp (connection, status)); - return MHD_YES; - - EXITIF_exit: - result = GNUNET_SYSERR; - //GNUNET_SCHEDULER_shutdown (); to a later stage, maybe - return MHD_NO; + else + EXITIF (GNUNET_OK != failure_resp (connection, status)); + return MHD_YES; + EXITIF_exit: + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); + return MHD_NO; } /** @@ -488,10 +664,38 @@ do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) MHD_stop_daemon (mhd); mhd = NULL; } + + if (NULL != db_conn) + { + MERCHANT_DB_disconnect (db_conn); + db_conn = NULL; + } + } + +/** + * Function called with information about who is auditing + * a particular mint and what key the mint is using. + * + * @param cls closure + * @param keys information about the various keys used + * by the mint + */ +void +keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) +{ + /* which kind of mint's keys a merchant should need? Sign + keys? It has already the mint's (master?) public key from + the conf file */ + return; + +} + + + /** * Main function that will be run by the scheduler. * @@ -505,10 +709,63 @@ run (void *cls, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *config) { - port = 9966; + char *keyfile; + unsigned int nmints; + unsigned int cnt; + struct MERCHANT_MintInfo *mint_infos; + void *keys_mgmt_cls; + mint_infos = NULL; + keyfile = NULL; + result = GNUNET_SYSERR; shutdown_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &do_shutdown, NULL); + EXITIF (GNUNET_SYSERR == (nmints = TALER_MERCHANT_parse_mints (config, + &mint_infos))); + EXITIF (NULL == (wire = TALER_MERCHANT_parse_wireformat_sepa (config))); + EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (config, + "merchant", + "KEYFILE", + &keyfile)); + EXITIF (NULL == (privkey = GNUNET_CRYPTO_eddsa_key_create_from_file (keyfile))); + EXITIF (NULL == (db_conn = MERCHANT_DB_connect (config))); + EXITIF (GNUNET_OK != MERCHANT_DB_initialize (db_conn, GNUNET_NO)); + EXITIF (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_number (config, + "merchant", + "port", + &port)); + EXITIF (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_string (config, + "merchant", + "hostname", + &hostname)); + + EXITIF (NULL == (mctx = TALER_MINT_init ())); + EXITIF (NULL == (mints_map = GNUNET_CONTAINER_multipeermap_create (nmints, GNUNET_YES))); + + for (cnt = 0; cnt < nmints; cnt++) + { + struct Mint *mint; + + mint = GNUNET_new (struct Mint); + mint->pubkey = mint_infos[cnt].pubkey; + /* port this to the new API */ + mint->conn = TALER_MINT_connect (mctx, + mint_infos[cnt].hostname, + &keys_mgmt_cb, + keys_mgmt_cls); /*<- safe?segfault friendly?*/ + + /* NOTE: the keys mgmt callback should roughly do what the following lines do */ + EXITIF (NULL == mint->conn); + + EXITIF (GNUNET_SYSERR == GNUNET_CONTAINER_multipeermap_put + (mints_map, + (struct GNUNET_PeerIdentity *) /* to retrieve now from cb's args -> */&mint->pubkey, + mint, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST)); + } + mhd = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY, port, @@ -518,12 +775,17 @@ run (void *cls, char *const *args, const char *cfgfile, EXITIF (NULL == mhd); + /* WARNING: a 'poll_mhd ()' call is here in the original merchant. Is that + mandatory ? */ + GNUNET_CRYPTO_hash (wire, sizeof (*wire), &h_wire); result = GNUNET_OK; EXITIF_exit: if (GNUNET_OK != result) GNUNET_SCHEDULER_shutdown (); - + GNUNET_free_non_null (keyfile); + if (GNUNET_OK != result) + GNUNET_SCHEDULER_shutdown (); } @@ -539,7 +801,10 @@ main (int argc, char *const *argv) { static const struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_OPTION_END + {'t', "temp", NULL, + gettext_noop ("Use temporary database tables"), GNUNET_NO, + &GNUNET_GETOPT_set_one, &dry}, + GNUNET_GETOPT_OPTION_END }; diff --git a/src/backend/taler-mint-httpd.h b/src/backend/taler-mint-httpd.h new file mode 100644 index 00000000..a54e5aa2 --- /dev/null +++ b/src/backend/taler-mint-httpd.h @@ -0,0 +1,127 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd.h + * @brief Global declarations for the mint + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + * + * FIXME: Consider which of these need to really be globals... + */ +#ifndef TALER_MINT_HTTPD_H +#define TALER_MINT_HTTPD_H + +#include <microhttpd.h> + +/** + * Which currency is used by this mint? + */ +extern char *TMH_mint_currency_string; + +/** + * The mint's configuration. + */ +extern struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Are we running in test mode? + */ +extern int TMH_test_mode; + +/** + * Main directory with mint data. + */ +extern char *TMH_mint_directory; + +/** + * In which format does this MINT expect wiring instructions? + */ +extern char *TMH_expected_wire_format; + +/** + * Master public key (according to the + * configuration in the mint directory). + */ +extern struct GNUNET_CRYPTO_EddsaPublicKey TMH_master_public_key; + +/** + * Private key of the mint we use to sign messages. + */ +extern struct GNUNET_CRYPTO_EddsaPrivateKey TMH_mint_private_signing_key; + +/** + * Our DB plugin. + */ +extern struct TALER_MINTDB_Plugin *TMH_plugin; + + +/** + * @brief Struct describing an URL and the handler for it. + */ +struct TMH_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 TMH_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/backend/taler-mint-httpd_admin.c b/src/backend/taler-mint-httpd_admin.c new file mode 100644 index 00000000..5fdfa58e --- /dev/null +++ b/src/backend/taler-mint-httpd_admin.c @@ -0,0 +1,163 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_admin.c + * @brief Handle /admin/ requests + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include "taler-mint-httpd_admin.h" +#include "taler-mint-httpd_parsing.h" +#include "taler-mint-httpd_responses.h" + + +/** + * Check permissions (we only allow access to /admin/ from loopback). + * + * @param connection connection to perform access check for + * @return #GNUNET_OK if permitted, + * #GNUNET_NO if denied and error was queued, + * #GNUNET_SYSERR if denied and we failed to report + */ +static int +check_permissions (struct MHD_Connection *connection) +{ + const union MHD_ConnectionInfo *ci; + const struct sockaddr *addr; + int res; + + ci = MHD_get_connection_info (connection, + MHD_CONNECTION_INFO_CLIENT_ADDRESS); + if (NULL == ci) + { + GNUNET_break (0); + res = TMH_RESPONSE_reply_internal_error (connection, + "Failed to verify client address"); + return (MHD_YES == res) ? GNUNET_NO : GNUNET_SYSERR; + } + addr = ci->client_addr; + switch (addr->sa_family) + { + case AF_INET: + { + const struct sockaddr_in *sin = (const struct sockaddr_in *) addr; + + if (INADDR_LOOPBACK != ntohl (sin->sin_addr.s_addr)) + { + res = TMH_RESPONSE_reply_permission_denied (connection, + "/admin/ only allowed via loopback"); + return (MHD_YES == res) ? GNUNET_NO : GNUNET_SYSERR; + } + break; + } + case AF_INET6: + { + const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *) addr; + + if (! IN6_IS_ADDR_LOOPBACK (&sin6->sin6_addr)) + { + res = TMH_RESPONSE_reply_permission_denied (connection, + "/admin/ only allowed via loopback"); + return (MHD_YES == res) ? GNUNET_NO : GNUNET_SYSERR; + } + break; + } + default: + GNUNET_break (0); + res = TMH_RESPONSE_reply_internal_error (connection, + "Unsupported AF"); + return (MHD_YES == res) ? GNUNET_NO : GNUNET_SYSERR; + } + return GNUNET_OK; +} + + + +/** + * Handle a "/admin/add/incoming" request. Parses the + * given "reserve_pub", "amount", "transaction" and "h_wire" + * details and adds the respective transaction to the database. + * + * @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 +TMH_ADMIN_handler_admin_add_incoming (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_Amount amount; + struct GNUNET_TIME_Absolute at; + json_t *wire; + json_t *root; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_fixed ("reserve_pub", &reserve_pub), + TMH_PARSE_member_amount ("amount", &amount), + TMH_PARSE_member_time_abs ("execution_date", &at), + TMH_PARSE_member_object ("wire", &wire), + TMH_PARSE_MEMBER_END + }; + int res; + + res = check_permissions (connection); + if (GNUNET_OK != res) + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + return MHD_NO; + if ( (GNUNET_NO == res) || (NULL == root) ) + return MHD_YES; + res = TMH_PARSE_json_data (connection, + root, + spec); + if (GNUNET_OK != res) + { + json_decref (root); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + if (GNUNET_YES != + TALER_json_validate_wireformat (TMH_expected_wire_format, + wire)) + { + TMH_PARSE_release_data (spec); + json_decref (root); + return TMH_RESPONSE_reply_arg_unknown (connection, + "wire"); + } + res = TMH_DB_execute_admin_add_incoming (connection, + &reserve_pub, + &amount, + at, + wire); + TMH_PARSE_release_data (spec); + json_decref (root); + return res; +} + +/* end of taler-mint-httpd_admin.c */ diff --git a/src/backend/taler-mint-httpd_admin.h b/src/backend/taler-mint-httpd_admin.h new file mode 100644 index 00000000..b8ca3ce5 --- /dev/null +++ b/src/backend/taler-mint-httpd_admin.h @@ -0,0 +1,46 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_admin.h + * @brief Handle /admin/ requests + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_ADMIN_H +#define TALER_MINT_HTTPD_ADMIN_H + +#include <microhttpd.h> +#include "taler-mint-httpd.h" + +/** + * Handle a "/admin/add/incoming" request. Parses the + * given "reserve_pub", "amount", "transaction" and "h_wire" + * details and adds the respective transaction to the database. + * + * @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 +TMH_ADMIN_handler_admin_add_incoming (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif diff --git a/src/backend/taler-mint-httpd_db.c b/src/backend/taler-mint-httpd_db.c new file mode 100644 index 00000000..4e91e7e7 --- /dev/null +++ b/src/backend/taler-mint-httpd_db.c @@ -0,0 +1,1444 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_db.c + * @brief High-level (transactional-layer) database operations for the mint. + * @author Christian Grothoff + */ +#include "platform.h" +#include <pthread.h> +#include <jansson.h> +#include "taler-mint-httpd_responses.h" +#include "taler-mint-httpd_keystate.h" + + +/** + * Calculate the total value of all transactions performed. + * Stores @a off plus the cost of all transactions in @a tl + * in @a ret. + * + * @param tl transaction list to process + * @param off offset to use as the starting value + * @param ret where the resulting total is to be stored + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static int +calculate_transaction_list_totals (struct TALER_MINTDB_TransactionList *tl, + const struct TALER_Amount *off, + struct TALER_Amount *ret) +{ + struct TALER_Amount spent = *off; + struct TALER_MINTDB_TransactionList *pos; + + for (pos = tl; NULL != pos; pos = pos->next) + { + switch (pos->type) + { + case TALER_MINTDB_TT_DEPOSIT: + if (GNUNET_OK != + TALER_amount_add (&spent, + &spent, + &pos->details.deposit->amount_with_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + break; + case TALER_MINTDB_TT_REFRESH_MELT: + if (GNUNET_OK != + TALER_amount_add (&spent, + &spent, + &pos->details.melt->amount_with_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + break; + case TALER_MINTDB_TT_LOCK: + /* should check if lock is still active, + and if it is for THIS operation; if + lock is inactive, delete it; if lock + is for THIS operation, ignore it; + if lock is for another operation, + count it! */ + GNUNET_assert (0); // FIXME: not implemented! (#3625) + return GNUNET_SYSERR; + } + } + *ret = spent; + return GNUNET_OK; +} + + +/** + * Execute a deposit. The validity of the coin and signature + * have already been checked. The database must now check that + * the coin is not (double or over) spent, and execute the + * transaction (record details, generate success or failure response). + * + * @param connection the MHD connection to handle + * @param deposit information about the deposit + * @return MHD result code + */ +int +TMH_DB_execute_deposit (struct MHD_Connection *connection, + const struct TALER_MINTDB_Deposit *deposit) +{ + struct TALER_MINTDB_Session *session; + struct TALER_MINTDB_TransactionList *tl; + struct TALER_Amount spent; + struct TALER_Amount value; + struct TALER_Amount amount_without_fee; + struct TMH_KS_StateHandle *mks; + struct TALER_MINTDB_DenominationKeyIssueInformation *dki; + int ret; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + if (GNUNET_YES == + TMH_plugin->have_deposit (TMH_plugin->cls, + session, + deposit)) + { + GNUNET_assert (GNUNET_OK == + TALER_amount_subtract (&amount_without_fee, + &deposit->amount_with_fee, + &deposit->deposit_fee)); + return TMH_RESPONSE_reply_deposit_success (connection, + &deposit->coin.coin_pub, + &deposit->h_wire, + &deposit->h_contract, + deposit->transaction_id, + deposit->timestamp, + deposit->refund_deadline, + &deposit->merchant_pub, + &amount_without_fee); + } + mks = TMH_KS_acquire (); + dki = TMH_KS_denomination_key_lookup (mks, + &deposit->coin.denom_pub, + TMH_KS_DKU_DEPOSIT); + TALER_amount_ntoh (&value, + &dki->issue.properties.value); + TMH_KS_release (mks); + + if (GNUNET_OK != + TMH_plugin->start (TMH_plugin->cls, + session)) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + /* fee for THIS transaction */ + spent = deposit->amount_with_fee; + /* add cost of all previous transactions */ + tl = TMH_plugin->get_coin_transactions (TMH_plugin->cls, + session, + &deposit->coin.coin_pub); + if (GNUNET_OK != + calculate_transaction_list_totals (tl, + &spent, + &spent)) + { + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, + tl); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + /* Check that cost of all transactions is smaller than + the value of the coin. */ + if (0 < TALER_amount_cmp (&spent, + &value)) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + ret = TMH_RESPONSE_reply_deposit_insufficient_funds (connection, + tl); + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, + tl); + return ret; + } + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, + tl); + + if (GNUNET_OK != + TMH_plugin->insert_deposit (TMH_plugin->cls, + session, + deposit)) + { + TALER_LOG_WARNING ("Failed to store /deposit information in database\n"); + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + if (GNUNET_OK != + TMH_plugin->commit (TMH_plugin->cls, + session)) + { + TALER_LOG_WARNING ("/deposit transaction commit failed\n"); + return TMH_RESPONSE_reply_commit_error (connection); + } + GNUNET_assert (GNUNET_OK == + TALER_amount_subtract (&amount_without_fee, + &deposit->amount_with_fee, + &deposit->deposit_fee)); + return TMH_RESPONSE_reply_deposit_success (connection, + &deposit->coin.coin_pub, + &deposit->h_wire, + &deposit->h_contract, + deposit->transaction_id, + deposit->timestamp, + deposit->refund_deadline, + &deposit->merchant_pub, + &amount_without_fee); +} + + +/** + * Execute a /withdraw/status. Given the public key of a reserve, + * return the associated transaction history. + * + * @param connection the MHD connection to handle + * @param reserve_pub public key of the reserve to check + * @return MHD result code + */ +int +TMH_DB_execute_withdraw_status (struct MHD_Connection *connection, + const struct TALER_ReservePublicKeyP *reserve_pub) +{ + struct TALER_MINTDB_Session *session; + struct TALER_MINTDB_ReserveHistory *rh; + int res; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + rh = TMH_plugin->get_reserve_history (TMH_plugin->cls, + session, + reserve_pub); + if (NULL == rh) + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s, s:s}", + "error", "Reserve not found", + "parameter", "withdraw_pub"); + res = TMH_RESPONSE_reply_withdraw_status_success (connection, + rh); + TMH_plugin->free_reserve_history (TMH_plugin->cls, + rh); + return res; +} + + +/** + * Execute a "/withdraw/sign". Given a reserve and a properly signed + * request to withdraw a coin, check the balance of the reserve and + * if it is sufficient, store the request and return the signed + * blinded envelope. + * + * @param connection the MHD connection to handle + * @param reserve public key of the reserve + * @param denomination_pub public key of the denomination requested + * @param blinded_msg blinded message to be signed + * @param blinded_msg_len number of bytes in @a blinded_msg + * @param signature signature over the withdraw request, to be stored in DB + * @return MHD result code + */ +int +TMH_DB_execute_withdraw_sign (struct MHD_Connection *connection, + const struct TALER_ReservePublicKeyP *reserve, + const struct TALER_DenominationPublicKey *denomination_pub, + const char *blinded_msg, + size_t blinded_msg_len, + const struct TALER_ReserveSignatureP *signature) +{ + struct TALER_MINTDB_Session *session; + struct TALER_MINTDB_ReserveHistory *rh; + const struct TALER_MINTDB_ReserveHistory *pos; + struct TMH_KS_StateHandle *key_state; + struct TALER_MINTDB_CollectableBlindcoin collectable; + struct TALER_MINTDB_DenominationKeyIssueInformation *dki; + struct TALER_MINTDB_DenominationKeyIssueInformation *tdki; + struct GNUNET_CRYPTO_rsa_Signature *sig; + struct TALER_Amount amount_required; + struct TALER_Amount deposit_total; + struct TALER_Amount withdraw_total; + struct TALER_Amount balance; + struct TALER_Amount value; + struct TALER_Amount fee_withdraw; + struct GNUNET_HashCode h_blind; + int res; + + GNUNET_CRYPTO_hash (blinded_msg, + blinded_msg_len, + &h_blind); + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + res = TMH_plugin->get_withdraw_info (TMH_plugin->cls, + session, + &h_blind, + &collectable); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + /* Don't sign again if we have already signed the coin */ + if (GNUNET_YES == res) + { + res = TMH_RESPONSE_reply_withdraw_sign_success (connection, + &collectable); + GNUNET_CRYPTO_rsa_signature_free (collectable.sig.rsa_signature); + GNUNET_CRYPTO_rsa_public_key_free (collectable.denom_pub.rsa_public_key); + return res; + } + GNUNET_assert (GNUNET_NO == res); + + /* Check if balance is sufficient */ + key_state = TMH_KS_acquire (); + dki = TMH_KS_denomination_key_lookup (key_state, + denomination_pub, + TMH_KS_DKU_WITHDRAW); + if (NULL == dki) + { + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", + "Denomination not found"); + } + if (GNUNET_OK != + TMH_plugin->start (TMH_plugin->cls, + session)) + { + GNUNET_break (0); + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + rh = TMH_plugin->get_reserve_history (TMH_plugin->cls, + session, + reserve); + if (NULL == rh) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_arg_unknown (connection, + "reserve_pub"); + } + + /* calculate amount required including fees */ + TALER_amount_ntoh (&value, + &dki->issue.properties.value); + TALER_amount_ntoh (&fee_withdraw, + &dki->issue.properties.fee_withdraw); + + if (GNUNET_OK != + TALER_amount_add (&amount_required, + &value, + &fee_withdraw)) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + /* calculate balance of the reserve */ + res = 0; + for (pos = rh; NULL != pos; pos = pos->next) + { + switch (pos->type) + { + case TALER_MINTDB_RO_BANK_TO_MINT: + if (0 == (res & 1)) + deposit_total = pos->details.bank->amount; + else + if (GNUNET_OK != + TALER_amount_add (&deposit_total, + &deposit_total, + &pos->details.bank->amount)) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + res |= 1; + break; + case TALER_MINTDB_RO_WITHDRAW_COIN: + tdki = TMH_KS_denomination_key_lookup (key_state, + &pos->details.withdraw->denom_pub, + TMH_KS_DKU_WITHDRAW); + TALER_amount_ntoh (&value, + &tdki->issue.properties.value); + if (0 == (res & 2)) + withdraw_total = value; + else + if (GNUNET_OK != + TALER_amount_add (&withdraw_total, + &withdraw_total, + &value)) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + res |= 2; + break; + } + } + if (0 == (res & 1)) + { + /* did not encounter any deposit operations, how can we have a reserve? */ + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + if (0 == (res & 2)) + { + /* did not encounter any withdraw operations, set to zero */ + TALER_amount_get_zero (deposit_total.currency, + &withdraw_total); + } + /* All reserve balances should be non-negative */ + GNUNET_assert (GNUNET_SYSERR != + TALER_amount_subtract (&balance, + &deposit_total, + &withdraw_total)); + if (0 < TALER_amount_cmp (&amount_required, + &balance)) + { + TMH_KS_release (key_state); + TMH_plugin->rollback (TMH_plugin->cls, + session); + res = TMH_RESPONSE_reply_withdraw_sign_insufficient_funds (connection, + rh); + TMH_plugin->free_reserve_history (TMH_plugin->cls, + rh); + return res; + } + TMH_plugin->free_reserve_history (TMH_plugin->cls, + rh); + + /* Balance is good, sign the coin! */ + sig = GNUNET_CRYPTO_rsa_sign (dki->denom_priv.rsa_private_key, + blinded_msg, + blinded_msg_len); + TMH_KS_release (key_state); + if (NULL == sig) + { + GNUNET_break (0); + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_error (connection, + "Internal error"); + } + collectable.sig.rsa_signature = sig; + collectable.denom_pub = *denomination_pub; + collectable.amount_with_fee = amount_required; + collectable.withdraw_fee = fee_withdraw; + collectable.reserve_pub = *reserve; + collectable.h_coin_envelope = h_blind; + collectable.reserve_sig = *signature; + if (GNUNET_OK != + TMH_plugin->insert_withdraw_info (TMH_plugin->cls, + session, + &collectable)) + { + GNUNET_break (0); + GNUNET_CRYPTO_rsa_signature_free (sig); + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + if (GNUNET_OK != + TMH_plugin->commit (TMH_plugin->cls, + session)) + { + TALER_LOG_WARNING ("/withdraw/sign transaction commit failed\n"); + return TMH_RESPONSE_reply_commit_error (connection); + } + res = TMH_RESPONSE_reply_withdraw_sign_success (connection, + &collectable); + GNUNET_CRYPTO_rsa_signature_free (sig); + return res; +} + + +/** + * Parse coin melt requests from a JSON object and write them to + * the database. + * + * @param connection the connection to send errors to + * @param session the database connection + * @param key_state the mint's key state + * @param session_hash hash identifying the refresh session + * @param coin_details details about the coin being melted + * @param oldcoin_index what is the number assigned to this coin + * @return #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, + struct TALER_MINTDB_Session *session, + const struct TMH_KS_StateHandle *key_state, + const struct GNUNET_HashCode *session_hash, + const struct TMH_DB_MeltDetails *coin_details, + uint16_t oldcoin_index) +{ + struct TALER_MINTDB_DenominationKeyInformationP *dki; + struct TALER_MINTDB_TransactionList *tl; + struct TALER_Amount coin_value; + struct TALER_Amount coin_residual; + struct TALER_Amount spent; + struct TALER_MINTDB_RefreshMelt melt; + int res; + + dki = &TMH_KS_denomination_key_lookup (key_state, + &coin_details->coin_info.denom_pub, + TMH_KS_DKU_DEPOSIT)->issue; + + if (NULL == dki) + return (MHD_YES == + TMH_RESPONSE_reply_arg_unknown (connection, + "denom_pub")) + ? GNUNET_NO : GNUNET_SYSERR; + + TALER_amount_ntoh (&coin_value, + &dki->properties.value); + /* fee for THIS transaction; the melt amount includes the fee! */ + spent = coin_details->melt_amount_with_fee; + /* add historic transaction costs of this coin */ + tl = TMH_plugin->get_coin_transactions (TMH_plugin->cls, + session, + &coin_details->coin_info.coin_pub); + if (GNUNET_OK != + calculate_transaction_list_totals (tl, + &spent, + &spent)) + { + GNUNET_break (0); + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, + tl); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + /* Refuse to refresh when the coin's value is insufficient + for the cost of all transactions. */ + if (TALER_amount_cmp (&coin_value, + &spent) < 0) + { + GNUNET_assert (GNUNET_OK == + TALER_amount_subtract (&coin_residual, + &spent, + &coin_details->melt_amount_with_fee)); + res = (MHD_YES == + TMH_RESPONSE_reply_refresh_melt_insufficient_funds (connection, + &coin_details->coin_info.coin_pub, + coin_value, + tl, + coin_details->melt_amount_with_fee, + coin_residual)) + ? GNUNET_NO : GNUNET_SYSERR; + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, + tl); + return res; + } + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, + tl); + + melt.coin = coin_details->coin_info; + melt.coin_sig = coin_details->melt_sig; + melt.session_hash = *session_hash; + melt.amount_with_fee = coin_details->melt_amount_with_fee; + if (GNUNET_OK != + TMH_plugin->insert_refresh_melt (TMH_plugin->cls, + session, + oldcoin_index, + &melt)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Execute a "/refresh/melt". We have been given a list of valid + * coins and a request to melt them into the given + * @a refresh_session_pub. Check that the coins all have the + * required value left and if so, store that they have been + * melted and confirm the melting operation to the client. + * + * @param connection the MHD connection to handle + * @param session_hash hash code of the session the coins are melted into + * @param num_new_denoms number of entries in @a denom_pubs, size of y-dimension of @a commit_coin array + * @param denom_pubs public keys of the coins we want to withdraw in the end + * @param coin_count number of entries in @a coin_melt_details, size of y-dimension of @a commit_link array + * @param coin_melt_details signatures and (residual) value of the respective coin should be melted + * @param commit_coin 2d array of coin commitments (what the mint is to sign + * once the "/refres/reveal" of cut and choose is done), + * x-dimension must be #TALER_CNC_KAPPA + * @param commit_link 2d array of coin link commitments (what the mint is + * to return via "/refresh/link" to enable linkage in the + * future) + * x-dimension must be #TALER_CNC_KAPPA + * @return MHD result code + */ +int +TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + unsigned int num_new_denoms, + const struct TALER_DenominationPublicKey *denom_pubs, + unsigned int coin_count, + const struct TMH_DB_MeltDetails *coin_melt_details, + struct TALER_MINTDB_RefreshCommitCoin *const* commit_coin, + struct TALER_MINTDB_RefreshCommitLinkP *const* commit_link) +{ + struct TMH_KS_StateHandle *key_state; + struct TALER_MINTDB_RefreshSession refresh_session; + struct TALER_MINTDB_Session *session; + int res; + unsigned int i; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + if (GNUNET_OK != + TMH_plugin->start (TMH_plugin->cls, + session)) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + res = TMH_plugin->get_refresh_session (TMH_plugin->cls, + session, + session_hash, + &refresh_session); + if (GNUNET_YES == res) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + res = TMH_RESPONSE_reply_refresh_melt_success (connection, + session_hash, + refresh_session.noreveal_index); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + if (GNUNET_SYSERR == res) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + /* store 'global' session data */ + refresh_session.num_oldcoins = coin_count; + refresh_session.num_newcoins = num_new_denoms; + refresh_session.noreveal_index + = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, + TALER_CNC_KAPPA); + if (GNUNET_OK != + (res = TMH_plugin->create_refresh_session (TMH_plugin->cls, + session, + session_hash, + &refresh_session))) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + /* Melt old coins and check that they had enough residual value */ + key_state = TMH_KS_acquire (); + for (i=0;i<coin_count;i++) + { + if (GNUNET_OK != + (res = refresh_accept_melts (connection, + session, + key_state, + session_hash, + &coin_melt_details[i], + i))) + { + TMH_KS_release (key_state); + TMH_plugin->rollback (TMH_plugin->cls, + session); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + } + TMH_KS_release (key_state); + + /* store requested new denominations */ + if (GNUNET_OK != + TMH_plugin->insert_refresh_order (TMH_plugin->cls, + session, + session_hash, + num_new_denoms, + denom_pubs)) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + for (i = 0; i < TALER_CNC_KAPPA; i++) + { + if (GNUNET_OK != + TMH_plugin->insert_refresh_commit_coins (TMH_plugin->cls, + session, + session_hash, + i, + num_new_denoms, + commit_coin[i])) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + } + for (i = 0; i < TALER_CNC_KAPPA; i++) + { + if (GNUNET_OK != + TMH_plugin->insert_refresh_commit_links (TMH_plugin->cls, + session, + session_hash, + i, + coin_count, + commit_link[i])) + { + TMH_plugin->rollback (TMH_plugin->cls, + session); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + } + + if (GNUNET_OK != + TMH_plugin->commit (TMH_plugin->cls, + session)) + { + TALER_LOG_WARNING ("/refresh/melt transaction commit failed\n"); + return TMH_RESPONSE_reply_commit_error (connection); + } + return TMH_RESPONSE_reply_refresh_melt_success (connection, + session_hash, + refresh_session.noreveal_index); +} + + +/** + * Send an error response with the details of the original melt + * commitment and the location of the mismatch. + * + * @param connection the MHD connection to handle + * @param session database connection to use + * @param session_hash hash of session to query + * @param off commitment offset to check + * @param index index of the mismatch + * @param object_name name of the object with the problem + * @return #GNUNET_NO if we generated the error message + * #GNUNET_SYSERR if we could not even generate an error message + */ +static int +send_melt_commitment_error (struct MHD_Connection *connection, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int off, + unsigned int index, + const char *object_name) +{ + struct TALER_MINTDB_MeltCommitment *mc; + int ret; + + mc = TMH_plugin->get_melt_commitment (TMH_plugin->cls, + session, + session_hash); + if (NULL == mc) + { + GNUNET_break (0); + return (MHD_YES == + TMH_RESPONSE_reply_internal_error (connection, + "Melt commitment assembly")) + ? GNUNET_NO : GNUNET_SYSERR; + } + ret = (MHD_YES == + TMH_RESPONSE_reply_refresh_reveal_missmatch (connection, + mc, + off, + index, + object_name)) + ? GNUNET_NO : GNUNET_SYSERR; + TMH_plugin->free_melt_commitment (TMH_plugin->cls, + mc); + return ret; +} + + +/** + * Check if the given @a transfer_privs correspond to an honest + * commitment for the given session. + * Checks that the transfer private keys match their commitments. + * Then derives the shared secret for each #TALER_CNC_KAPPA, and check that they match. + * + * @param connection the MHD connection to handle + * @param session database connection to use + * @param session_hash hash of session to query + * @param off commitment offset to check + * @param num_oldcoins size of the @a transfer_privs and @a melts arrays + * @param transfer_privs private transfer keys + * @param melts array of melted coins + * @param num_newcoins number of newcoins being generated + * @param denom_pubs array of @a num_newcoins keys for the new coins + * @return #GNUNET_OK if the committment was honest, + * #GNUNET_NO if there was a problem and we generated an error message + * #GNUNET_SYSERR if we could not even generate an error message + */ +static int +check_commitment (struct MHD_Connection *connection, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int off, + unsigned int num_oldcoins, + const struct TALER_TransferPrivateKeyP *transfer_privs, + const struct TALER_MINTDB_RefreshMelt *melts, + unsigned int num_newcoins, + const struct TALER_DenominationPublicKey *denom_pubs) +{ + unsigned int j; + struct TALER_LinkSecretP last_shared_secret; + int secret_initialized = GNUNET_NO; + struct TALER_MINTDB_RefreshCommitLinkP *commit_links; + struct TALER_MINTDB_RefreshCommitCoin *commit_coins; + + commit_links = GNUNET_malloc (num_oldcoins * + sizeof (struct TALER_MINTDB_RefreshCommitLinkP)); + if (GNUNET_OK != + TMH_plugin->get_refresh_commit_links (TMH_plugin->cls, + session, + session_hash, + off, + num_oldcoins, + commit_links)) + { + GNUNET_break (0); + GNUNET_free (commit_links); + return (MHD_YES == TMH_RESPONSE_reply_internal_db_error (connection)) + ? GNUNET_NO : GNUNET_SYSERR; + } + + for (j = 0; j < num_oldcoins; j++) + { + struct TALER_LinkSecretP shared_secret; + struct TALER_TransferPublicKeyP transfer_pub_check; + + GNUNET_CRYPTO_ecdhe_key_get_public (&transfer_privs[j].ecdhe_priv, + &transfer_pub_check.ecdhe_pub); + if (0 != + memcmp (&transfer_pub_check, + &commit_links[j].transfer_pub, + sizeof (struct TALER_TransferPublicKeyP))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "transfer keys do not match\n"); + GNUNET_free (commit_links); + return send_melt_commitment_error (connection, + session, + session_hash, + off, + j, + "transfer key"); + } + + if (GNUNET_OK != + TALER_link_decrypt_secret (&commit_links[j].shared_secret_enc, + &transfer_privs[j], + &melts[j].coin.coin_pub, + &shared_secret)) + { + GNUNET_free (commit_links); + return (MHD_YES == + TMH_RESPONSE_reply_internal_error (connection, + "Transfer secret decryption error")) + ? GNUNET_NO : 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"); + GNUNET_free (commit_links); + return send_melt_commitment_error (connection, + session, + session_hash, + off, + j, + "transfer secret"); + } + } + GNUNET_break (GNUNET_YES == secret_initialized); + GNUNET_free (commit_links); + + /* Check that the commitments for all new coins were correct */ + commit_coins = GNUNET_malloc (num_newcoins * + sizeof (struct TALER_MINTDB_RefreshCommitCoin)); + + if (GNUNET_OK != + TMH_plugin->get_refresh_commit_coins (TMH_plugin->cls, + session, + session_hash, + off, + num_newcoins, + commit_coins)) + { + GNUNET_break (0); + GNUNET_free (commit_coins); + return (MHD_YES == TMH_RESPONSE_reply_internal_db_error (connection)) + ? GNUNET_NO : GNUNET_SYSERR; + } + + for (j = 0; j < num_newcoins; j++) + { + struct TALER_RefreshLinkDecrypted *link_data; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_HashCode h_msg; + char *buf; + size_t buf_len; + + link_data = TALER_refresh_decrypt (commit_coins[j].refresh_link, + &last_shared_secret); + if (NULL == link_data) + { + GNUNET_break (0); + GNUNET_free (commit_coins); + return (MHD_YES == TMH_RESPONSE_reply_internal_error (connection, + "Decryption error")) + ? GNUNET_NO : GNUNET_SYSERR; + } + + GNUNET_CRYPTO_eddsa_key_get_public (&link_data->coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + GNUNET_CRYPTO_hash (&coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP), + &h_msg); + if (0 == (buf_len = + GNUNET_CRYPTO_rsa_blind (&h_msg, + link_data->blinding_key.rsa_blinding_key, + denom_pubs[j].rsa_public_key, + &buf))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "blind failed\n"); + GNUNET_free (commit_coins); + return (MHD_YES == TMH_RESPONSE_reply_internal_error (connection, + "Blinding error")) + ? GNUNET_NO : GNUNET_SYSERR; + } + + if ( (buf_len != commit_coins[j].coin_ev_size) || + (0 != memcmp (buf, + commit_coins[j].coin_ev, + buf_len)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "blind envelope does not match for k=%u, old=%d\n", + off, + (int) j); + GNUNET_free (commit_coins); + return send_melt_commitment_error (connection, + session, + session_hash, + off, + j, + "envelope"); + } + GNUNET_free (buf); + } + GNUNET_free (commit_coins); + + return GNUNET_OK; +} + + +/** + * Mint a coin as part of a refresh operation. Obtains the + * envelope from the database and performs the signing operation. + * + * @param connection the MHD connection to handle + * @param session database connection to use + * @param session_hash hash of session to query + * @param key_state key state to lookup denomination pubs + * @param denom_pub denomination key for the coin to create + * @param commit_coin the coin that was committed + * @param coin_off number of the coin + * @return NULL on error, otherwise signature over the coin + */ +static struct TALER_DenominationSignature +refresh_mint_coin (struct MHD_Connection *connection, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + struct TMH_KS_StateHandle *key_state, + const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_MINTDB_RefreshCommitCoin *commit_coin, + unsigned int coin_off) +{ + struct TALER_MINTDB_DenominationKeyIssueInformation *dki; + struct TALER_DenominationSignature ev_sig; + + dki = TMH_KS_denomination_key_lookup (key_state, + denom_pub, + TMH_KS_DKU_WITHDRAW); + if (NULL == dki) + { + GNUNET_break (0); + ev_sig.rsa_signature = NULL; + return ev_sig; + } + ev_sig.rsa_signature + = GNUNET_CRYPTO_rsa_sign (dki->denom_priv.rsa_private_key, + commit_coin->coin_ev, + commit_coin->coin_ev_size); + if (NULL == ev_sig.rsa_signature) + { + GNUNET_break (0); + return ev_sig; + } + if (GNUNET_OK != + TMH_plugin->insert_refresh_out (TMH_plugin->cls, + session, + session_hash, + coin_off, + &ev_sig)) + { + GNUNET_break (0); + GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature); + ev_sig.rsa_signature = NULL; + } + return ev_sig; +} + + +/** + * Execute a "/refresh/reveal". The client is revealing to us the + * transfer keys for @a #TALER_CNC_KAPPA-1 sets of coins. Verify that the + * revealed transfer keys would allow linkage to the blinded coins, + * and if so, return the signed coins for corresponding to the set of + * coins that was not chosen. + * + * @param connection the MHD connection to handle + * @param session_hash hash identifying the refresh session + * @param num_oldcoins size of y-dimension of @a transfer_privs array + * @param transfer_privs array with the revealed transfer keys, + * x-dimension must be #TALER_CNC_KAPPA - 1 + * @return MHD result code + */ +int +TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + unsigned int num_oldcoins, + struct TALER_TransferPrivateKeyP **transfer_privs) +{ + int res; + struct TALER_MINTDB_Session *session; + struct TALER_MINTDB_RefreshSession refresh_session; + struct TMH_KS_StateHandle *key_state; + struct TALER_MINTDB_RefreshMelt *melts; + struct TALER_DenominationPublicKey *denom_pubs; + struct TALER_DenominationSignature *ev_sigs; + struct TALER_MINTDB_RefreshCommitCoin *commit_coins; + unsigned int i; + unsigned int j; + unsigned int off; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + res = TMH_plugin->get_refresh_session (TMH_plugin->cls, + session, + session_hash, + &refresh_session); + if (GNUNET_NO == res) + return TMH_RESPONSE_reply_arg_invalid (connection, + "session_hash"); + if (GNUNET_SYSERR == res) + return TMH_RESPONSE_reply_internal_db_error (connection); + if (0 == refresh_session.num_oldcoins) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + melts = GNUNET_malloc (refresh_session.num_oldcoins * + sizeof (struct TALER_MINTDB_RefreshMelt)); + for (j=0;j<refresh_session.num_oldcoins;j++) + { + if (GNUNET_OK != + TMH_plugin->get_refresh_melt (TMH_plugin->cls, + session, + session_hash, + j, + &melts[j])) + { + GNUNET_break (0); + GNUNET_free (melts); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + } + denom_pubs = GNUNET_malloc (refresh_session.num_newcoins * + sizeof (struct TALER_DenominationPublicKey)); + if (GNUNET_OK != + TMH_plugin->get_refresh_order (TMH_plugin->cls, + session, + session_hash, + refresh_session.num_newcoins, + denom_pubs)) + { + GNUNET_break (0); + GNUNET_free (denom_pubs); + GNUNET_free (melts); + return (MHD_YES == TMH_RESPONSE_reply_internal_db_error (connection)) + ? GNUNET_NO : GNUNET_SYSERR; + } + + + off = 0; + for (i=0;i<TALER_CNC_KAPPA - 1;i++) + { + if (i == refresh_session.noreveal_index) + off = 1; + if (GNUNET_OK != + (res = check_commitment (connection, + session, + session_hash, + i + off, + refresh_session.num_oldcoins, + transfer_privs[i + off], + melts, + refresh_session.num_newcoins, + denom_pubs))) + { + for (j=0;j<refresh_session.num_newcoins;j++) + GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key); + GNUNET_free (denom_pubs); + GNUNET_free (melts); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + } + GNUNET_free (melts); + + /* Client request OK, start transaction */ + if (GNUNET_OK != + TMH_plugin->start (TMH_plugin->cls, + session)) + { + GNUNET_break (0); + for (j=0;j<refresh_session.num_newcoins;j++) + GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key); + GNUNET_free (denom_pubs); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + + commit_coins = GNUNET_malloc (refresh_session.num_newcoins * + sizeof (struct TALER_MINTDB_RefreshCommitCoin)); + if (GNUNET_OK != + TMH_plugin->get_refresh_commit_coins (TMH_plugin->cls, + session, + session_hash, + refresh_session.noreveal_index, + refresh_session.num_newcoins, + commit_coins)) + { + GNUNET_break (0); + GNUNET_free (commit_coins); + for (j=0;j<refresh_session.num_newcoins;j++) + GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key); + GNUNET_free (denom_pubs); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + ev_sigs = GNUNET_malloc (refresh_session.num_newcoins * + sizeof (struct TALER_DenominationSignature)); + key_state = TMH_KS_acquire (); + for (j=0;j<refresh_session.num_newcoins;j++) + { + ev_sigs[j] = refresh_mint_coin (connection, + session, + session_hash, + key_state, + &denom_pubs[j], + &commit_coins[j], + j); + if (NULL == ev_sigs[j].rsa_signature) + { + TMH_KS_release (key_state); + for (i=0;i<j;i++) + GNUNET_CRYPTO_rsa_signature_free (ev_sigs[i].rsa_signature); + GNUNET_free (ev_sigs); + for (j=0;j<refresh_session.num_newcoins;j++) + GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key); + GNUNET_free (denom_pubs); + GNUNET_free (commit_coins); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + } + TMH_KS_release (key_state); + for (j=0;j<refresh_session.num_newcoins;j++) + GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key); + GNUNET_free (denom_pubs); + GNUNET_free (commit_coins); + + if (GNUNET_OK != + TMH_plugin->commit (TMH_plugin->cls, + session)) + { + TALER_LOG_WARNING ("/refresh/reveal transaction commit failed\n"); + for (i=0;i<refresh_session.num_newcoins;i++) + GNUNET_CRYPTO_rsa_signature_free (ev_sigs[i].rsa_signature); + GNUNET_free (ev_sigs); + return TMH_RESPONSE_reply_commit_error (connection); + } + + res = TMH_RESPONSE_reply_refresh_reveal_success (connection, + refresh_session.num_newcoins, + ev_sigs); + for (i=0;i<refresh_session.num_newcoins;i++) + GNUNET_CRYPTO_rsa_signature_free (ev_sigs[i].rsa_signature); + GNUNET_free (ev_sigs); + return res; +} + + +/** + * Closure for #handle_transfer_data(). + */ +struct HTD_Context +{ + + /** + * Session link data we collect. + */ + struct TMH_RESPONSE_LinkSessionInfo *sessions; + + /** + * Database session. Nothing to do with @a sessions. + */ + struct TALER_MINTDB_Session *session; + + /** + * MHD connection, for queueing replies. + */ + struct MHD_Connection *connection; + + /** + * Number of sessions the coin was melted into. + */ + unsigned int num_sessions; + + /** + * How are we expected to proceed. #GNUNET_SYSERR if we + * failed to return an error (should return #MHD_NO). + * #GNUNET_NO if we succeeded in queueing an MHD error + * (should return #MHD_YES from #TMH_execute_refresh_link), + * #GNUNET_OK if we should call #TMH_RESPONSE_reply_refresh_link_success(). + */ + int status; +}; + + +/** + * Function called with the session hashes and transfer secret + * information for a given coin. Gets the linkage data and + * builds the reply for the client. + * + * + * @param cls closure, a `struct HTD_Context` + * @param session_hash a session the coin was melted in + * @param transfer_pub public transfer key for the session + * @param shared_secret_enc set to shared secret for the session + */ +static void +handle_transfer_data (void *cls, + const struct GNUNET_HashCode *session_hash, + const struct TALER_TransferPublicKeyP *transfer_pub, + const struct TALER_EncryptedLinkSecretP *shared_secret_enc) +{ + struct HTD_Context *ctx = cls; + struct TALER_MINTDB_LinkDataList *ldl; + struct TMH_RESPONSE_LinkSessionInfo *lsi; + + if (GNUNET_OK != ctx->status) + return; + ldl = TMH_plugin->get_link_data_list (TMH_plugin->cls, + ctx->session, + session_hash); + if (NULL == ldl) + { + GNUNET_break (0); + ctx->status = GNUNET_NO; + if (MHD_NO == + TMH_RESPONSE_reply_json_pack (ctx->connection, + MHD_HTTP_NOT_FOUND, + "{s:s}", + "error", + "link data not found (link)")) + ctx->status = GNUNET_SYSERR; + return; + } + GNUNET_array_grow (ctx->sessions, + ctx->num_sessions, + ctx->num_sessions + 1); + lsi = &ctx->sessions[ctx->num_sessions - 1]; + lsi->transfer_pub = *transfer_pub; + lsi->shared_secret_enc = *shared_secret_enc; + lsi->ldl = ldl; +} + + +/** + * Execute a "/refresh/link". Returns the linkage information that + * will allow the owner of a coin to follow the refresh trail to + * the refreshed coin. + * + * @param connection the MHD connection to handle + * @param coin_pub public key of the coin to link + * @return MHD result code + */ +int +TMH_DB_execute_refresh_link (struct MHD_Connection *connection, + const struct TALER_CoinSpendPublicKeyP *coin_pub) +{ + struct HTD_Context ctx; + int res; + unsigned int i; + + if (NULL == (ctx.session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + ctx.connection = connection; + ctx.num_sessions = 0; + ctx.sessions = NULL; + ctx.status = GNUNET_OK; + res = TMH_plugin->get_transfer (TMH_plugin->cls, + ctx.session, + coin_pub, + &handle_transfer_data, + &ctx); + if (GNUNET_SYSERR == ctx.status) + { + res = MHD_NO; + goto cleanup; + } + if (GNUNET_NO == ctx.status) + { + res = MHD_YES; + goto cleanup; + } + GNUNET_assert (GNUNET_OK == ctx.status); + if (0 == ctx.num_sessions) + return TMH_RESPONSE_reply_arg_unknown (connection, + "coin_pub"); + res = TMH_RESPONSE_reply_refresh_link_success (connection, + ctx.num_sessions, + ctx.sessions); + cleanup: + for (i=0;i<ctx.num_sessions;i++) + TMH_plugin->free_link_data_list (TMH_plugin->cls, + ctx.sessions[i].ldl); + GNUNET_free (ctx.sessions); + return res; +} + + +/** + * Add an incoming transaction to the database. Checks if the + * transaction is fresh (not a duplicate) and if so adds it to + * the database. + * + * @param connection the MHD connection to handle + * @param reserve_pub public key of the reserve + * @param amount amount to add to the reserve + * @param execution_time when did we receive the wire transfer + * @param wire details about the wire transfer + * @return MHD result code + */ +int +TMH_DB_execute_admin_add_incoming (struct MHD_Connection *connection, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Absolute execution_time, + json_t *wire) +{ + struct TALER_MINTDB_Session *session; + int ret; + + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode))) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + ret = TMH_plugin->reserves_in_insert (TMH_plugin->cls, + session, + reserve_pub, + amount, + execution_time, + wire); + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_internal_db_error (connection); + } + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:s}", + "status", + (GNUNET_OK == ret) + ? "NEW" + : "DUP"); +} + + +/* end of taler-mint-httpd_db.c */ diff --git a/src/backend/taler-mint-httpd_db.h b/src/backend/taler-mint-httpd_db.h new file mode 100644 index 00000000..8a171153 --- /dev/null +++ b/src/backend/taler-mint-httpd_db.h @@ -0,0 +1,190 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mint/taler-mint-httpd_db.h + * @brief High-level (transactional-layer) database operations for the mint + * @author Chrisitan Grothoff + */ +#ifndef TALER_MINT_HTTPD_DB_H +#define TALER_MINT_HTTPD_DB_H + +#include <microhttpd.h> +#include "taler_mintdb_plugin.h" + + +/** + * Execute a "/deposit". The validity of the coin and signature + * have already been checked. The database must now check that + * the coin is not (double or over) spent, and execute the + * transaction (record details, generate success or failure response). + * + * @param connection the MHD connection to handle + * @param deposit information about the deposit + * @return MHD result code + */ +int +TMH_DB_execute_deposit (struct MHD_Connection *connection, + const struct TALER_MINTDB_Deposit *deposit); + + +/** + * Execute a "/withdraw/status". Given the public key of a reserve, + * return the associated transaction history. + * + * @param connection the MHD connection to handle + * @param reserve_pub public key of the reserve to check + * @return MHD result code + */ +int +TMH_DB_execute_withdraw_status (struct MHD_Connection *connection, + const struct TALER_ReservePublicKeyP *reserve_pub); + + +/** + * Execute a "/withdraw/sign". Given a reserve and a properly signed + * request to withdraw a coin, check the balance of the reserve and + * if it is sufficient, store the request and return the signed + * blinded envelope. + * + * @param connection the MHD connection to handle + * @param reserve public key of the reserve + * @param denomination_pub public key of the denomination requested + * @param blinded_msg blinded message to be signed + * @param blinded_msg_len number of bytes in @a blinded_msg + * @param signature signature over the withdraw request, to be stored in DB + * @return MHD result code + */ +int +TMH_DB_execute_withdraw_sign (struct MHD_Connection *connection, + const struct TALER_ReservePublicKeyP *reserve, + const struct TALER_DenominationPublicKey *denomination_pub, + const char *blinded_msg, + size_t blinded_msg_len, + const struct TALER_ReserveSignatureP *signature); + + +/** + * @brief Details about a melt operation of an individual coin. + */ +struct TMH_DB_MeltDetails +{ + + /** + * Information about the coin being melted. + */ + struct TALER_CoinPublicInfo coin_info; + + /** + * Signature allowing the melt (using + * a `struct TALER_MINTDB_RefreshMeltConfirmSignRequestBody`) to sign over. + */ + struct TALER_CoinSpendSignatureP melt_sig; + + /** + * How much of the coin's value did the client allow to be melted? + * This amount includes the fees, so the final amount contributed + * to the melt is this value minus the fee for melting the coin. + */ + struct TALER_Amount melt_amount_with_fee; +}; + + +/** + * Execute a "/refresh/melt". We have been given a list of valid + * coins and a request to melt them into the given + * @a refresh_session_pub. Check that the coins all have the + * required value left and if so, store that they have been + * melted and confirm the melting operation to the client. + * + * @param connection the MHD connection to handle + * @param session_hash hash code of the session the coins are melted into + * @param num_new_denoms number of entries in @a denom_pubs, size of y-dimension of @a commit_coin array + * @param denom_pubs array of public denomination keys for the refresh (?) + * @param coin_count number of entries in @ a coin_melt_details, size of y-dimension of @a commit_link array + * @param coin_melt_details signatures and (residual) value of and information about the respective coin to be melted + * @param commit_coin 2d array of coin commitments (what the mint is to sign + * once the "/refres/reveal" of cut and choose is done) + * @param commit_link 2d array of coin link commitments (what the mint is + * to return via "/refresh/link" to enable linkage in the + * future) + * @return MHD result code + */ +int +TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + unsigned int num_new_denoms, + const struct TALER_DenominationPublicKey *denom_pubs, + unsigned int coin_count, + const struct TMH_DB_MeltDetails *coin_melt_details, + struct TALER_MINTDB_RefreshCommitCoin *const* commit_coin, + struct TALER_MINTDB_RefreshCommitLinkP *const* commit_link); + + +/** + * Execute a "/refresh/reveal". The client is revealing to us the + * transfer keys for #TALER_CNC_KAPPA-1 sets of coins. Verify that the + * revealed transfer keys would allow linkage to the blinded coins, + * and if so, return the signed coins for corresponding to the set of + * coins that was not chosen. + * + * @param connection the MHD connection to handle + * @param session_hash hash over the refresh session + * @param num_oldcoins size of y-dimension of @a transfer_privs array + * @param transfer_privs array with the revealed transfer keys, #TALER_CNC_KAPPA is 1st-dimension + * @return MHD result code + */ +int +TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + unsigned int num_oldcoins, + struct TALER_TransferPrivateKeyP **transfer_privs); + + +/** + * Execute a "/refresh/link". Returns the linkage information that + * will allow the owner of a coin to follow the refresh trail to the + * refreshed coin. + * + * @param connection the MHD connection to handle + * @param coin_pub public key of the coin to link + * @return MHD result code + */ +int +TMH_DB_execute_refresh_link (struct MHD_Connection *connection, + const struct TALER_CoinSpendPublicKeyP *coin_pub); + + + +/** + * Add an incoming transaction to the database. + * + * @param connection the MHD connection to handle + * @param reserve_pub public key of the reserve + * @param amount amount to add to the reserve + * @param execution_time when did we receive the wire transfer + * @param wire details about the wire transfer + * @return MHD result code + */ +int +TMH_DB_execute_admin_add_incoming (struct MHD_Connection *connection, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Absolute execution_time, + json_t *wire); + + +#endif +/* TALER_MINT_HTTPD_DB_H */ diff --git a/src/backend/taler-mint-httpd_deposit.c b/src/backend/taler-mint-httpd_deposit.c new file mode 100644 index 00000000..5725cd1c --- /dev/null +++ b/src/backend/taler-mint-httpd_deposit.c @@ -0,0 +1,270 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_deposit.c + * @brief Handle /deposit requests; parses the POST and JSON and + * verifies the coin signature before handing things off + * to the database. + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + * + * TODO: + * - ugly if-construction for deposit type + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <pthread.h> +#include "taler-mint-httpd_parsing.h" +#include "taler-mint-httpd_deposit.h" +#include "taler-mint-httpd_responses.h" +#include "taler-mint-httpd_keystate.h" + + +/** + * We have parsed the JSON information about the deposit, do some + * basic sanity checks (especially that the signature on the coin is + * valid, and that this type of coin exists) and then execute the + * deposit. + * + * @param connection the MHD connection to handle + * @param deposit information about the deposit + * @return MHD result code + */ +static int +verify_and_execute_deposit (struct MHD_Connection *connection, + const struct TALER_MINTDB_Deposit *deposit) +{ + struct TMH_KS_StateHandle *key_state; + struct TALER_DepositRequestPS dr; + struct TALER_MINTDB_DenominationKeyIssueInformation *dki; + struct TALER_Amount fee_deposit; + + dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); + dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS)); + dr.h_contract = deposit->h_contract; + dr.h_wire = deposit->h_wire; + dr.timestamp = GNUNET_TIME_absolute_hton (deposit->timestamp); + dr.refund_deadline = GNUNET_TIME_absolute_hton (deposit->refund_deadline); + dr.transaction_id = GNUNET_htonll (deposit->transaction_id); + TALER_amount_hton (&dr.amount_with_fee, + &deposit->amount_with_fee); + TALER_amount_hton (&dr.deposit_fee, + &deposit->deposit_fee); + dr.merchant = deposit->merchant_pub; + dr.coin_pub = deposit->coin.coin_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT, + &dr.purpose, + &deposit->csig.eddsa_signature, + &deposit->coin.coin_pub.eddsa_pub)) + { + TALER_LOG_WARNING ("Invalid signature on /deposit request\n"); + return TMH_RESPONSE_reply_signature_invalid (connection, + "coin_sig"); + } + /* check denomination exists and is valid */ + key_state = TMH_KS_acquire (); + dki = TMH_KS_denomination_key_lookup (key_state, + &deposit->coin.denom_pub, + TMH_KS_DKU_DEPOSIT); + if (NULL == dki) + { + TMH_KS_release (key_state); + TALER_LOG_WARNING ("Unknown denomination key in /deposit request\n"); + return TMH_RESPONSE_reply_arg_unknown (connection, + "denom_pub"); + } + /* check coin signature */ + if (GNUNET_YES != + TALER_test_coin_valid (&deposit->coin)) + { + TALER_LOG_WARNING ("Invalid coin passed for /deposit\n"); + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_signature_invalid (connection, + "ub_sig"); + } + TALER_amount_ntoh (&fee_deposit, + &dki->issue.properties.fee_deposit); + if (0 < TALER_amount_cmp (&fee_deposit, + &deposit->amount_with_fee)) + { + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_external_error (connection, + "deposited amount smaller than depositing fee"); + } + TMH_KS_release (key_state); + + return TMH_DB_execute_deposit (connection, + deposit); +} + + +/** + * Handle a "/deposit" request. This function parses the + * JSON information and then calls #verify_and_execute_deposit() + * to verify the signatures and execute the deposit. + * + * @param connection the MHD connection to handle + * @param root root of the posted JSON + * @param amount how much should be deposited + * @param wire json describing the wire details (?) + * @return MHD result code + */ +static int +parse_and_handle_deposit_request (struct MHD_Connection *connection, + const json_t *root, + const struct TALER_Amount *amount, + json_t *wire) +{ + int res; + struct TALER_MINTDB_Deposit deposit; + struct TALER_MINTDB_DenominationKeyIssueInformation *dki; + struct TMH_KS_StateHandle *ks; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_denomination_public_key ("denom_pub", &deposit.coin.denom_pub), + TMH_PARSE_member_denomination_signature ("ub_sig", &deposit.coin.denom_sig), + TMH_PARSE_member_fixed ("coin_pub", &deposit.coin.coin_pub), + TMH_PARSE_member_fixed ("merchant_pub", &deposit.merchant_pub), + TMH_PARSE_member_fixed ("H_contract", &deposit.h_contract), + TMH_PARSE_member_fixed ("H_wire", &deposit.h_wire), + TMH_PARSE_member_fixed ("coin_sig", &deposit.csig), + TMH_PARSE_member_uint64 ("transaction_id", &deposit.transaction_id), + TMH_PARSE_member_time_abs ("timestamp", &deposit.timestamp), + TMH_PARSE_member_time_abs ("refund_deadline", &deposit.refund_deadline), + TMH_PARSE_MEMBER_END + }; + + memset (&deposit, 0, sizeof (deposit)); + res = TMH_PARSE_json_data (connection, + root, + spec); + if (GNUNET_SYSERR == res) + return MHD_NO; /* hard failure */ + if (GNUNET_NO == res) + return MHD_YES; /* failure */ + + if (GNUNET_YES != + TALER_json_validate_wireformat (TMH_expected_wire_format, + wire)) + { + TMH_PARSE_release_data (spec); + return TMH_RESPONSE_reply_arg_unknown (connection, + "wire"); + } + if (GNUNET_OK != + TALER_hash_json (wire, + &deposit.h_wire)) + { + TALER_LOG_WARNING ("Failed to parse JSON wire format specification for /deposit request\n"); + TMH_PARSE_release_data (spec); + return TMH_RESPONSE_reply_arg_invalid (connection, + "wire"); + } + ks = TMH_KS_acquire (); + dki = TMH_KS_denomination_key_lookup (ks, + &deposit.coin.denom_pub, + TMH_KS_DKU_DEPOSIT); + if (NULL == dki) + { + TMH_KS_release (ks); + TMH_PARSE_release_data (spec); + return TMH_RESPONSE_reply_arg_unknown (connection, + "denom_pub"); + } + TALER_amount_ntoh (&deposit.deposit_fee, + &dki->issue.properties.fee_deposit); + TMH_KS_release (ks); + deposit.wire = wire; + deposit.amount_with_fee = *amount; + if (-1 == TALER_amount_cmp (&deposit.amount_with_fee, + &deposit.deposit_fee)) + { + /* Total amount smaller than fee, invalid */ + TMH_PARSE_release_data (spec); + return TMH_RESPONSE_reply_arg_invalid (connection, + "f"); + } + res = verify_and_execute_deposit (connection, + &deposit); + TMH_PARSE_release_data (spec); + return res; +} + + +/** + * Handle a "/deposit" request. Parses the JSON in the post to find + * the "type" (either DIRECT_DEPOSIT or INCREMENTAL_DEPOSIT), and, if + * successful, passes the JSON data to + * #parse_and_handle_deposit_request() to further check the details + * of the operation specified in the "wire" field of the JSON data. + * If everything checks out, this will ultimately lead to the + * "/deposit" being executed, or rejected. + * + * @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 +TMH_DEPOSIT_handler_deposit (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + json_t *json; + json_t *wire; + int res; + struct TALER_Amount amount; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_object ("wire", &wire), + TMH_PARSE_member_amount ("f", &amount), + TMH_PARSE_MEMBER_END + }; + + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &json); + if (GNUNET_SYSERR == res) + return MHD_NO; + if ( (GNUNET_NO == res) || (NULL == json) ) + return MHD_YES; + res = TMH_PARSE_json_data (connection, + json, + spec); + if (GNUNET_OK != res) + { + json_decref (json); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + res = parse_and_handle_deposit_request (connection, + json, + &amount, + wire); + TMH_PARSE_release_data (spec); + json_decref (json); + return res; +} + + +/* end of taler-mint-httpd_deposit.c */ diff --git a/src/backend/taler-mint-httpd_deposit.h b/src/backend/taler-mint-httpd_deposit.h new file mode 100644 index 00000000..c2d3fe13 --- /dev/null +++ b/src/backend/taler-mint-httpd_deposit.h @@ -0,0 +1,54 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_deposit.h + * @brief Handle /deposit requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_DEPOSIT_H +#define TALER_MINT_HTTPD_DEPOSIT_H + +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + + +/** + * Handle a "/deposit" request. Parses the JSON in the post to find + * the "type" (either DIRECT_DEPOSIT or INCREMENTAL_DEPOSIT), and, if + * successful, passes the JSON data to + * #parse_and_handle_deposit_request() to further check the details + * of the operation specified in the "wire" field of the JSON data. + * If everything checks out, this will ultimately lead to the + * "/deposit" being executed, or rejected. + * + * @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 +TMH_DEPOSIT_handler_deposit (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif diff --git a/src/backend/taler-mint-httpd_keystate.c b/src/backend/taler-mint-httpd_keystate.c new file mode 100644 index 00000000..ec09ab44 --- /dev/null +++ b/src/backend/taler-mint-httpd_keystate.c @@ -0,0 +1,867 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_keystate.c + * @brief management of our coin signing keys + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <pthread.h> +#include "taler-mint-httpd_keystate.h" +#include "taler_mintdb_plugin.h" + + +/** + * Snapshot of the (coin and signing) keys (including private keys) of + * the mint. There can be multiple instances of this struct, as it is + * reference counted and only destroyed once the last user is done + * with it. The current instance is acquired using + * #TMH_KS_acquire(). Using this function increases the + * reference count. The contents of this structure (except for the + * reference counter) should be considered READ-ONLY until it is + * ultimately destroyed (as there can be many concurrent users). + */ +struct TMH_KS_StateHandle +{ + /** + * JSON array with denomination keys. (Currently not really used + * after initialization.) + */ + json_t *denom_keys_array; + + /** + * JSON array with signing keys. (Currently not really used + * after initialization.) + */ + json_t *sign_keys_array; + + /** + * Cached JSON text that the mint will send for a "/keys" request. + * Includes our @e TMH_master_public_key public key, the signing and + * denomination keys as well as the @e reload_time. + */ + char *keys_json; + + /** + * Mapping from denomination keys to denomination key issue struct. + * Used to lookup the key by hash. + */ + struct GNUNET_CONTAINER_MultiHashMap *denomkey_map; + + /** + * Hash context we used to combine the hashes of all denomination + * keys into one big hash. + */ + struct GNUNET_HashContext *hash_context; + + /** + * When did we initiate the key reloading? + */ + struct GNUNET_TIME_Absolute reload_time; + + /** + * When is the next key invalid and we have to reload? (We also + * reload on SIGUSR1.) + */ + struct GNUNET_TIME_Absolute next_reload; + + /** + * Mint signing key that should be used currently. + */ + struct TALER_MINTDB_PrivateSigningKeyInformationP current_sign_key_issue; + + /** + * Reference count. The struct is released when the RC hits zero. + */ + unsigned int refcnt; +}; + + +/** + * Mint key state. Never use directly, instead access via + * #TMH_KS_acquire() and #TMH_KS_release(). + */ +static struct TMH_KS_StateHandle *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 pk public key of the denomination key + * @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_DenominationPublicKey *pk, + const struct TALER_MINTDB_DenominationKeyInformationP *dki) +{ + struct TALER_Amount value; + struct TALER_Amount fee_withdraw; + struct TALER_Amount fee_deposit; + struct TALER_Amount fee_refresh; + + TALER_amount_ntoh (&value, + &dki->properties.value); + TALER_amount_ntoh (&fee_withdraw, + &dki->properties.fee_withdraw); + TALER_amount_ntoh (&fee_deposit, + &dki->properties.fee_deposit); + TALER_amount_ntoh (&fee_refresh, + &dki->properties.fee_refresh); + return + json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o}", + "master_sig", + TALER_json_from_data (&dki->signature, + sizeof (struct GNUNET_CRYPTO_EddsaSignature)), + "stamp_start", + TALER_json_from_abs (GNUNET_TIME_absolute_ntoh (dki->properties.start)), + "stamp_expire_withdraw", + TALER_json_from_abs (GNUNET_TIME_absolute_ntoh (dki->properties.expire_withdraw)), + "stamp_expire_deposit", + TALER_json_from_abs (GNUNET_TIME_absolute_ntoh (dki->properties.expire_spend)), + "stamp_expire_legal", + TALER_json_from_abs (GNUNET_TIME_absolute_ntoh (dki->properties.expire_legal)), + "denom_pub", + TALER_json_from_rsa_public_key (pk->rsa_public_key), + "value", + TALER_json_from_amount (&value), + "fee_withdraw", + TALER_json_from_amount (&fee_withdraw), + "fee_deposit", + TALER_json_from_amount (&fee_deposit), + "fee_refresh", + TALER_json_from_amount (&fee_refresh)); +} + + +/** + * 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_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "mint_keys", + "lookahead_provide", + "time value required"); + GNUNET_assert (0); + } + return rel; +} + + +/** + * Iterator for (re)loading/initializing 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_MINTDB_DenominationKeyIssueInformation *dki) +{ + struct TMH_KS_StateHandle *ctx = cls; + struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Absolute horizon; + struct GNUNET_HashCode denom_key_hash; + struct TALER_MINTDB_DenominationKeyIssueInformation *d2; + struct TALER_MINTDB_Session *session; + int res; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Loading denomination key `%s'\n", + alias); + horizon = GNUNET_TIME_relative_to_absolute (TALER_MINT_conf_duration_provide ()); + if (GNUNET_TIME_absolute_ntoh (dki->issue.properties.start).abs_value_us > + horizon.abs_value_us) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Skipping future denomination key `%s'\n", + alias); + return GNUNET_OK; + } + now = GNUNET_TIME_absolute_get (); + if (GNUNET_TIME_absolute_ntoh (dki->issue.properties.expire_spend).abs_value_us < + now.abs_value_us) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Skipping expired denomination key `%s'\n", + alias); + return GNUNET_OK; + } + + GNUNET_CRYPTO_rsa_public_key_hash (dki->denom_pub.rsa_public_key, + &denom_key_hash); + GNUNET_CRYPTO_hash_context_read (ctx->hash_context, + &denom_key_hash, + sizeof (struct GNUNET_HashCode)); + session = TMH_plugin->get_session (TMH_plugin->cls, + TMH_test_mode); + if (NULL == session) + return GNUNET_SYSERR; + /* Try to insert DKI into DB until we succeed; note that if the DB + failure is persistent, this code may loop forever (as there is no + sane alternative, we cannot continue without the DKI being in the + DB). */ + res = GNUNET_SYSERR; + while (GNUNET_OK != res) + { + res = TMH_plugin->start (TMH_plugin->cls, + session); + if (GNUNET_OK != res) + { + /* Transaction start failed!? Very bad error, log and retry */ + GNUNET_break (0); + continue; + } + res = TMH_plugin->get_denomination_info (TMH_plugin->cls, + session, + &dki->denom_pub, + NULL); + if (GNUNET_SYSERR == res) + { + /* Fetch failed!? Very bad error, log and retry */ + GNUNET_break (0); + TMH_plugin->rollback (TMH_plugin->cls, + session); + continue; + } + if (GNUNET_OK == res) + { + /* Record exists, we're good, just exit */ + TMH_plugin->rollback (TMH_plugin->cls, + session); + break; + } + res = TMH_plugin->insert_denomination_info (TMH_plugin->cls, + session, + &dki->denom_pub, + &dki->issue); + if (GNUNET_OK != res) + { + /* Insert failed!? Very bad error, log and retry */ + GNUNET_break (0); + TMH_plugin->rollback (TMH_plugin->cls, + session); + continue; + } + res = TMH_plugin->commit (TMH_plugin->cls, + session); + /* If commit succeeded, we're done, otherwise we retry; this + time without logging, as theroetically commits can fail + in a transactional DB due to concurrent activities that + cannot be reconciled. This should be rare for DKIs, but + as it is possible we just retry until we succeed. */ + } + + d2 = GNUNET_new (struct TALER_MINTDB_DenominationKeyIssueInformation); + d2->issue = dki->issue; + d2->denom_priv.rsa_private_key + = GNUNET_CRYPTO_rsa_private_key_dup (dki->denom_priv.rsa_private_key); + d2->denom_pub.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_dup (dki->denom_pub.rsa_public_key); + res = GNUNET_CONTAINER_multihashmap_put (ctx->denomkey_map, + &denom_key_hash, + d2, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); + if (GNUNET_OK != res) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Duplicate denomination key `%s'\n", + alias); + GNUNET_CRYPTO_rsa_private_key_free (d2->denom_priv.rsa_private_key); + GNUNET_CRYPTO_rsa_public_key_free (d2->denom_pub.rsa_public_key); + GNUNET_free (d2); + return GNUNET_OK; + } + json_array_append_new (ctx->denom_keys_array, + denom_key_issue_to_json (&dki->denom_pub, + &dki->issue)); + return GNUNET_OK; +} + + +/** + * 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_MintSigningKeyValidityPS *ski) +{ + return + json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}", + "stamp_start", + TALER_json_from_abs (GNUNET_TIME_absolute_ntoh (ski->start)), + "stamp_expire", + TALER_json_from_abs (GNUNET_TIME_absolute_ntoh (ski->expire)), + "stamp_end", + TALER_json_from_abs (GNUNET_TIME_absolute_ntoh (ski->end)), + "master_pub", + TALER_json_from_data (&ski->master_public_key, + sizeof (struct TALER_MasterPublicKeyP)), + "master_sig", + TALER_json_from_data (&ski->signature, + sizeof (struct TALER_MasterSignatureP)), + "key", + TALER_json_from_data (&ski->signkey_pub, + sizeof (struct TALER_MintPublicKeyP))); +} + + +/** + * Iterator for sign keys. + * + * @param cls closure + * @param filename name of the file the key came from + * @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 char *filename, + const struct TALER_MINTDB_PrivateSigningKeyInformationP *ski) +{ + struct TMH_KS_StateHandle *ctx = cls; + struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Absolute horizon; + + horizon = GNUNET_TIME_relative_to_absolute (TALER_MINT_conf_duration_provide ()); + if (GNUNET_TIME_absolute_ntoh (ski->issue.start).abs_value_us > + horizon.abs_value_us) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Skipping future signing key `%s'\n", + filename); + return GNUNET_OK; + } + now = GNUNET_TIME_absolute_get (); + if (GNUNET_TIME_absolute_ntoh (ski->issue.expire).abs_value_us < + now.abs_value_us) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Skipping expired signing key `%s'\n", + filename); + return GNUNET_OK; + } + + /* The signkey is valid at this time, check if it's more recent than + what we have so far! */ + if ( (GNUNET_TIME_absolute_ntoh (ctx->current_sign_key_issue.issue.start).abs_value_us < + GNUNET_TIME_absolute_ntoh (ski->issue.start).abs_value_us) && + (GNUNET_TIME_absolute_ntoh (ski->issue.start).abs_value_us < + now.abs_value_us) ) + { + /* We use the most recent one, if it is valid now (not just in the near future) */ + ctx->current_sign_key_issue = *ski; + } + json_array_append_new (ctx->sign_keys_array, + sign_key_issue_to_json (&ski->issue)); + + return GNUNET_OK; +} + + +/** + * Iterator for freeing denomination keys. + * + * @param cls closure with the `struct TMH_KS_StateHandle` + * @param key key for the denomination key + * @param value coin details + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +static int +free_denom_key (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct TALER_MINTDB_DenominationKeyIssueInformation *dki = value; + + GNUNET_CRYPTO_rsa_private_key_free (dki->denom_priv.rsa_private_key); + GNUNET_CRYPTO_rsa_public_key_free (dki->denom_pub.rsa_public_key); + GNUNET_free (dki); + return GNUNET_OK; +} + + +/** + * Release key state, free if necessary (if reference count gets to zero). + * Internal method used when the mutex is already held. + * + * @param key_state the key state to release + */ +static void +TMH_KS_release_ (struct TMH_KS_StateHandle *key_state) +{ + GNUNET_assert (0 < key_state->refcnt); + key_state->refcnt--; + if (0 == key_state->refcnt) + { + if (NULL != key_state->denom_keys_array) + { + json_decref (key_state->denom_keys_array); + key_state->denom_keys_array = NULL; + } + if (NULL != key_state->sign_keys_array) + { + json_decref (key_state->sign_keys_array); + key_state->sign_keys_array = NULL; + } + if (NULL != key_state->denomkey_map) + { + GNUNET_CONTAINER_multihashmap_iterate (key_state->denomkey_map, + &free_denom_key, + key_state); + GNUNET_CONTAINER_multihashmap_destroy (key_state->denomkey_map); + key_state->denomkey_map = NULL; + } + GNUNET_free_non_null (key_state->keys_json); + GNUNET_free (key_state); + } +} + + +/** + * Release key state, free if necessary (if reference count gets to zero). + * + * @param key_state the key state to release + */ +void +TMH_KS_release (struct TMH_KS_StateHandle *key_state) +{ + GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex)); + TMH_KS_release_ (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 #TMH_KS_acquire(), a matching call + * to #TMH_KS_release() must be made. + * + * @return the key state + */ +struct TMH_KS_StateHandle * +TMH_KS_acquire (void) +{ + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct TMH_KS_StateHandle *key_state; + json_t *keys; + struct TALER_MintKeySetPS ks; + struct TALER_MintSignatureP sig; + + GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex)); + if ( (NULL != internal_key_state) && + (internal_key_state->next_reload.abs_value_us <= now.abs_value_us) ) + { + TMH_KS_release_ (internal_key_state); + internal_key_state = NULL; + } + if (NULL == internal_key_state) + { + key_state = GNUNET_new (struct TMH_KS_StateHandle); + key_state->hash_context = GNUNET_CRYPTO_hash_context_start (); + 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); + key_state->reload_time = GNUNET_TIME_absolute_get (); + TALER_round_abs_time (&key_state->reload_time); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Loading keys from `%s'\n", + TMH_mint_directory); + TALER_MINTDB_denomination_keys_iterate (TMH_mint_directory, + &reload_keys_denom_iter, + key_state); + TALER_MINTDB_signing_keys_iterate (TMH_mint_directory, + &reload_keys_sign_iter, + key_state); + ks.purpose.size = htonl (sizeof (ks)); + ks.purpose.purpose = htonl (TALER_SIGNATURE_MINT_KEY_SET); + ks.list_issue_date = GNUNET_TIME_absolute_hton (key_state->reload_time); + GNUNET_CRYPTO_hash_context_finish (key_state->hash_context, + &ks.hc); + key_state->hash_context = NULL; + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&key_state->current_sign_key_issue.signkey_priv.eddsa_priv, + &ks.purpose, + &sig.eddsa_signature)); + key_state->next_reload = GNUNET_TIME_absolute_ntoh (key_state->current_sign_key_issue.issue.expire); + if (0 == key_state->next_reload.abs_value_us) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "No valid signing key found!\n"); + + keys = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}", + "master_public_key", + TALER_json_from_data (&TMH_master_public_key, + 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), + "eddsa_pub", TALER_json_from_data (&key_state->current_sign_key_issue.issue.signkey_pub, + sizeof (struct TALER_MintPublicKeyP)), + "eddsa_sig", TALER_json_from_data (&sig, + sizeof (struct TALER_MintSignatureP))); + key_state->sign_keys_array = NULL; + key_state->denom_keys_array = NULL; + key_state->keys_json = json_dumps (keys, + JSON_INDENT (2)); + json_decref (keys); + internal_key_state = key_state; + } + key_state = internal_key_state; + key_state->refcnt++; + 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 state to look in + * @param denom_pub denomination public key + * @param use purpose for which the key is being located + * @return the denomination key issue, + * or NULL if denom_pub could not be found + */ +struct TALER_MINTDB_DenominationKeyIssueInformation * +TMH_KS_denomination_key_lookup (const struct TMH_KS_StateHandle *key_state, + const struct TALER_DenominationPublicKey *denom_pub, + enum TMH_KS_DenominationKeyUse use) +{ + struct GNUNET_HashCode hc; + struct TALER_MINTDB_DenominationKeyIssueInformation *dki; + struct GNUNET_TIME_Absolute now; + + GNUNET_CRYPTO_rsa_public_key_hash (denom_pub->rsa_public_key, + &hc); + dki = GNUNET_CONTAINER_multihashmap_get (key_state->denomkey_map, + &hc); + if (NULL == dki) + return NULL; + now = GNUNET_TIME_absolute_get (); + if (now.abs_value_us < + GNUNET_TIME_absolute_ntoh (dki->issue.properties.start).abs_value_us) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Not returning DKI for %s, as start time is in the future\n", + GNUNET_h2s (&hc)); + return NULL; + } + now = GNUNET_TIME_absolute_get (); + switch (use) + { + case TMH_KS_DKU_WITHDRAW: + if (now.abs_value_us > + GNUNET_TIME_absolute_ntoh (dki->issue.properties.expire_withdraw).abs_value_us) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Not returning DKI for %s, as time to create coins has passed\n", + GNUNET_h2s (&hc)); + return NULL; + } + break; + case TMH_KS_DKU_DEPOSIT: + if (now.abs_value_us > + GNUNET_TIME_absolute_ntoh (dki->issue.properties.expire_spend).abs_value_us) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Not returning DKI for %s, as time to spend coin has passed\n", + GNUNET_h2s (&hc)); + return NULL; + } + break; + } + return dki; +} + + +/** + * Handle a signal, writing relevant signal numbers to the pipe. + * + * @param signal_number the signal number + */ +static void +handle_signal (int signal_number) +{ + ssize_t res; + char c = signal_number; + + res = write (reload_pipe[1], + &c, + 1); + if ( (res < 0) && + (EINTR != errno) ) + { + GNUNET_break (0); + return; + } + if (0 == res) + { + GNUNET_break (0); + return; + } +} + + +/** + * Call #handle_signal() to pass the received signal via + * the control pipe. + */ +static void +handle_sigusr1 () +{ + handle_signal (SIGUSR1); +} + + +/** + * Call #handle_signal() to pass the received signal via + * the control pipe. + */ +static void +handle_sigint () +{ + handle_signal (SIGINT); +} + + +/** + * Call #handle_signal() to pass the received signal via + * the control pipe. + */ +static void +handle_sigterm () +{ + handle_signal (SIGTERM); +} + + +/** + * Call #handle_signal() to pass the received signal via + * the control pipe. + */ +static void +handle_sighup () +{ + handle_signal (SIGHUP); +} + + +/** + * Read signals from a pipe in a loop, and reload keys from disk if + * SIGUSR1 is received, terminate if SIGTERM/SIGINT is received, and + * restart if SIGHUP is received. + * + * @return #GNUNET_SYSERR on errors, + * #GNUNET_OK to terminate normally + * #GNUNET_NO to restart an update version of the binary + */ +int +TMH_KS_loop (void) +{ + struct GNUNET_SIGNAL_Context *sigusr1; + struct GNUNET_SIGNAL_Context *sigterm; + struct GNUNET_SIGNAL_Context *sigint; + struct GNUNET_SIGNAL_Context *sighup; + int ret; + + if (0 != pipe (reload_pipe)) + { + fprintf (stderr, + "Failed to create pipe.\n"); + return GNUNET_SYSERR; + } + sigusr1 = GNUNET_SIGNAL_handler_install (SIGUSR1, + &handle_sigusr1); + sigterm = GNUNET_SIGNAL_handler_install (SIGTERM, + &handle_sigterm); + sigint = GNUNET_SIGNAL_handler_install (SIGINT, + &handle_sigint); + sighup = GNUNET_SIGNAL_handler_install (SIGHUP, + &handle_sighup); + + ret = 0; + while (0 == ret) + { + char c; + ssize_t res; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "(re-)loading keys\n"); + if (NULL != internal_key_state) + { + TMH_KS_release (internal_key_state); + internal_key_state = NULL; + } + /* This will re-initialize 'internal_key_state' with + an initial refcnt of 1 */ + (void) TMH_KS_acquire (); + +read_again: + errno = 0; + res = read (reload_pipe[0], + &c, + 1); + if ((res < 0) && (EINTR != errno)) + { + GNUNET_break (0); + ret = GNUNET_SYSERR; + break; + } + if (EINTR == errno) + goto read_again; + switch (c) + { + case SIGUSR1: + /* reload internal key state, we do this in the loop */ + break; + case SIGTERM: + case SIGINT: + /* terminate */ + ret = GNUNET_OK; + break; + case SIGHUP: + /* restart updated binary */ + ret = GNUNET_NO; + break; + default: + /* unexpected character */ + GNUNET_break (0); + break; + } + } + if (NULL != internal_key_state) + { + TMH_KS_release (internal_key_state); + internal_key_state = NULL; + } + GNUNET_SIGNAL_handler_uninstall (sigusr1); + GNUNET_SIGNAL_handler_uninstall (sigterm); + GNUNET_SIGNAL_handler_uninstall (sigint); + GNUNET_SIGNAL_handler_uninstall (sighup); + return ret; +} + + +/** + * Sign the message in @a purpose with the mint's signing key. + * + * @param purpose the message to sign + * @param[out] pub set to the current public signing key of the mint + * @param[out] sig signature over purpose using current signing key + */ +void +TMH_KS_sign (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose, + struct TALER_MintPublicKeyP *pub, + struct TALER_MintSignatureP *sig) + +{ + struct TMH_KS_StateHandle *key_state; + + key_state = TMH_KS_acquire (); + *pub = key_state->current_sign_key_issue.issue.signkey_pub; + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&key_state->current_sign_key_issue.signkey_priv.eddsa_priv, + purpose, + &sig->eddsa_signature)); + TMH_KS_release (key_state); +} + + +/** + * 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 +TMH_KS_handler_keys (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct TMH_KS_StateHandle *key_state; + struct MHD_Response *response; + int ret; + + key_state = TMH_KS_acquire (); + response = MHD_create_response_from_buffer (strlen (key_state->keys_json), + key_state->keys_json, + MHD_RESPMEM_MUST_COPY); + TMH_KS_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; +} + + +/* end of taler-mint-httpd_keystate.c */ diff --git a/src/backend/taler-mint-httpd_keystate.h b/src/backend/taler-mint-httpd_keystate.h new file mode 100644 index 00000000..62b041e9 --- /dev/null +++ b/src/backend/taler-mint-httpd_keystate.h @@ -0,0 +1,142 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mint/taler-mint-httpd_keystate.h + * @brief management of our private signing keys (denomination keys) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_KEYSTATE_H +#define TALER_MINT_HTTPD_KEYSTATE_H + +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" +#include "taler_mintdb_lib.h" + + +/** + * Snapshot of the (coin and signing) + * keys (including private keys) of the mint. + */ +struct TMH_KS_StateHandle; + + +/** + * Acquire the key state of the mint. Updates keys if necessary. + * For every call to #TMH_KS_acquire(), a matching call + * to #TMH_KS_release() must be made. + * + * @return the key state + */ +struct TMH_KS_StateHandle * +TMH_KS_acquire (void); + + +/** + * Release key state, free if necessary (if reference count gets to zero). + * + * @param key_state the key state to release + */ +void +TMH_KS_release (struct TMH_KS_StateHandle *key_state); + + +/** + * Denomination key lookups can be for signing of fresh coins + * or to validate signatures on existing coins. As the validity + * periods for a key differ, the caller must specify which + * use is relevant for the current operation. + */ +enum TMH_KS_DenominationKeyUse { + + /** + * The key is to be used for a /withdraw/sign or /refresh (mint) + * operation. + */ + TMH_KS_DKU_WITHDRAW, + + /** + * The key is to be usd for a /deposit or /refresh (melt) operation. + */ + TMH_KS_DKU_DEPOSIT + +}; + + +/** + * Look up the issue for a denom public key. Note that the result + * is only valid while the @a key_state is not released! + * + * @param key_state state to look in + * @param denom_pub denomination public key + * @param use purpose for which the key is being located + * @return the denomination key issue, + * or NULL if denom_pub could not be found (or is not valid at this time for the given @a use) + */ +struct TALER_MINTDB_DenominationKeyIssueInformation * +TMH_KS_denomination_key_lookup (const struct TMH_KS_StateHandle *key_state, + const struct TALER_DenominationPublicKey *denom_pub, + enum TMH_KS_DenominationKeyUse use); + + +/** + * Read signals from a pipe in a loop, and reload keys from disk if + * SIGUSR1 is received, terminate if SIGTERM/SIGINT is received, and + * restart if SIGHUP is received. + * + * @return #GNUNET_SYSERR on errors, + * #GNUNET_OK to terminate normally + * #GNUNET_NO to restart an update version of the binary + */ +int +TMH_KS_loop (void); + + +/** + * Sign the message in @a purpose with the mint's signing + * key. + * + * @param purpose the message to sign + * @param[out] pub set to the current public signing key of the mint + * @param[out] sig signature over purpose using current signing key + */ +void +TMH_KS_sign (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose, + struct TALER_MintPublicKeyP *pub, + struct TALER_MintSignatureP *sig); + + +/** + * 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 +TMH_KS_handler_keys (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +#endif diff --git a/src/backend/taler-mint-httpd_mhd.c b/src/backend/taler-mint-httpd_mhd.c new file mode 100644 index 00000000..b4e3c1f6 --- /dev/null +++ b/src/backend/taler-mint-httpd_mhd.c @@ -0,0 +1,152 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd_mhd.c + * @brief helpers for MHD interaction; these are TALER_MINT_handler_ functions + * that generate simple MHD replies that do not require any real operations + * to be performed (error handling, static pages, etc.) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <pthread.h> +#include "taler-mint-httpd_responses.h" +#include "taler-mint-httpd.h" +#include "taler-mint-httpd_mhd.h" +#include "taler-mint-httpd_responses.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 +TMH_MHD_handler_static_response (struct TMH_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 +TMH_MHD_handler_agpl_redirect (struct TMH_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 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 +TMH_MHD_handler_send_json_pack_error (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + return TMH_RESPONSE_reply_json_pack (connection, + rh->response_code, + "{s:s}", + "error", + rh->data); +} + + +/* end of taler-mint-httpd_mhd.c */ diff --git a/src/backend/taler-mint-httpd_mhd.h b/src/backend/taler-mint-httpd_mhd.h new file mode 100644 index 00000000..a9f575df --- /dev/null +++ b/src/backend/taler-mint-httpd_mhd.h @@ -0,0 +1,111 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd_mhd.h + * @brief helpers for MHD interaction, used to generate simple responses + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_MHD_H +#define TALER_MINT_HTTPD_MHD_H +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_static_response (struct TMH_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 +TMH_MHD_handler_agpl_redirect (struct TMH_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 +TMH_MHD_helper_send_json_pack (struct TMH_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 +TMH_MHD_handler_send_json_pack_error (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +#endif diff --git a/src/backend/taler-mint-httpd_parsing.c b/src/backend/taler-mint-httpd_parsing.c new file mode 100644 index 00000000..1844fa88 --- /dev/null +++ b/src/backend/taler-mint-httpd_parsing.c @@ -0,0 +1,1123 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd_parsing.c + * @brief functions to parse incoming requests (MHD arguments and JSON snippets) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ + +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler-mint-httpd_parsing.h" +#include "taler-mint-httpd_responses.h" + + +/** + * Initial size for POST request buffer. + */ +#define REQUEST_BUFFER_INITIAL (2*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 data_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 + */ +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); + GNUNET_free (buf->data); + buf->data = new_buf; + buf->alloc = new_size; + } + memcpy (buf->data + buf->fill, data, data_size); + buf->fill += data_size; + return GNUNET_OK; +} + + +/** + * Release all memory allocated for the variable-size fields in + * the parser specification. + * + * @param spec specification to free + * @param spec_len number of items in @a spec to look at + */ +static void +release_data (struct TMH_PARSE_FieldSpecification *spec, + unsigned int spec_len) +{ + unsigned int i; + + for (i=0; i < spec_len; i++) + { + switch (spec[i].command) + { + case TMH_PARSE_JNC_FIELD: + GNUNET_break (0); + return; + case TMH_PARSE_JNC_INDEX: + GNUNET_break (0); + return; + case TMH_PARSE_JNC_RET_DATA: + break; + case TMH_PARSE_JNC_RET_DATA_VAR: + if (NULL != spec[i].destination) + { + GNUNET_free (* (void**) spec[i].destination); + *(void**) spec[i].destination = NULL; + *spec[i].destination_size_out = 0; + } + break; + case TMH_PARSE_JNC_RET_TYPED_JSON: + { + json_t *json; + + json = *(json_t **) spec[i].destination; + if (NULL != json) + { + json_decref (json); + *(json_t**) spec[i].destination = NULL; + } + } + break; + case TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY: + { + struct TALER_DenominationPublicKey *pk; + + pk = spec[i].destination; + if (NULL != pk->rsa_public_key) + { + GNUNET_CRYPTO_rsa_public_key_free (pk->rsa_public_key); + pk->rsa_public_key = NULL; + } + } + break; + case TMH_PARSE_JNC_RET_RSA_SIGNATURE: + { + struct TALER_DenominationSignature *sig; + + sig = spec[i].destination; + if (NULL != sig->rsa_signature) + { + GNUNET_CRYPTO_rsa_signature_free (sig->rsa_signature); + sig->rsa_signature = NULL; + } + } + break; + case TMH_PARSE_JNC_RET_AMOUNT: + memset (spec[i].destination, + 0, + sizeof (struct TALER_Amount)); + break; + case TMH_PARSE_JNC_RET_TIME_ABSOLUTE: + break; + case TMH_PARSE_JNC_RET_UINT64: + break; + } + } +} + + +/** + * Process a POST request containing a JSON object. This function + * realizes an MHD POST processor that will (incrementally) process + * JSON data uploaded to the HTTP server. It will store the required + * state in the @a con_cls, which must be cleaned up using + * #TMH_PARSE_post_cleanup_callback(). + * + * @param connection the MHD connection + * @param con_cls the closure (points to a `struct Buffer *`) + * @param upload_data the POST data + * @param upload_data_size number of bytes in @a upload_data + * @param json the JSON object for a completed request + * @return + * #GNUNET_YES if json object was parsed or at least + * may be parsed in the future (call again); + * `*json` will be NULL if we need to be called again, + * and non-NULL if we are done. + * #GNUNET_NO is request incomplete or invalid + * (error message was generated) + * #GNUNET_SYSERR on internal error + * (we could not even queue an error message, + * close HTTP session with MHD_NO) + */ +int +TMH_PARSE_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; + + *json = NULL; + 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 (MHD_NO == + TMH_RESPONSE_reply_internal_error (connection, + "out of memory")) + ? GNUNET_SYSERR : GNUNET_NO; + } + /* everything OK, wait for more POST data */ + *upload_data_size = 0; + *con_cls = r; + return GNUNET_YES; + } + 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 */ + *con_cls = NULL; + buffer_deinit (r); + GNUNET_free (r); + return (MHD_NO == + TMH_RESPONSE_reply_request_too_large (connection)) + ? GNUNET_SYSERR : GNUNET_NO; + } + /* everything OK, wait for more POST data */ + *upload_data_size = 0; + return GNUNET_YES; + } + + /* We have seen the whole request. */ + + *json = json_loadb (r->data, + r->fill, + 0, + NULL); + if (NULL == *json) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse JSON request body\n"); + return (MHD_YES == + TMH_RESPONSE_reply_invalid_json (connection)) + ? GNUNET_NO : GNUNET_SYSERR; + } + buffer_deinit (r); + GNUNET_free (r); + *con_cls = NULL; + + return GNUNET_YES; +} + + +/** + * Function called whenever we are done with a request + * to clean up our state. + * + * @param con_cls value as it was left by + * #TMH_PARSE_post_json(), to be cleaned up + */ +void +TMH_PARSE_post_cleanup_callback (void *con_cls) +{ + struct Buffer *r = con_cls; + + if (NULL != r) + { + buffer_deinit (r); + GNUNET_free (r); + } +} + + +/** + * Extract 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 +TMH_PARSE_mhd_request_arg_data (struct MHD_Connection *connection, + const char *param_name, + void *out_data, + size_t out_size) +{ + const char *str; + + str = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + param_name); + if (NULL == str) + { + return (MHD_NO == + TMH_RESPONSE_reply_arg_missing (connection, param_name)) + ? GNUNET_SYSERR : GNUNET_NO; + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (str, + strlen (str), + out_data, + out_size)) + return (MHD_NO == + TMH_RESPONSE_reply_arg_invalid (connection, param_name)) + ? GNUNET_SYSERR : GNUNET_NO; + return GNUNET_OK; +} + + +/** + * Extraxt variable-size base32crockford encoded data from request. + * + * Queues an error response to the connection if the parameter is missing + * or the encoding is invalid. + * + * @param connection the MHD connection + * @param param_name the name of the parameter with the key + * @param[out] out_data pointer to allocate buffer and store the result + * @param[out] out_size set to the size of the buffer allocated in @a out_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 +TMH_PARSE_mhd_request_var_arg_data (struct MHD_Connection *connection, + const char *param_name, + void **out_data, + size_t *out_size) +{ + const char *str; + size_t slen; + size_t olen; + void *out; + + str = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + param_name); + if (NULL == str) + { + return (MHD_NO == + TMH_RESPONSE_reply_arg_missing (connection, param_name)) + ? GNUNET_SYSERR : GNUNET_NO; + } + slen = strlen (str); + olen = (slen * 5) / 8; + out = GNUNET_malloc (olen); + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (str, + strlen (str), + out, + olen)) + { + GNUNET_free (out); + *out_size = 0; + return (MHD_NO == + TMH_RESPONSE_reply_arg_invalid (connection, param_name)) + ? GNUNET_SYSERR : GNUNET_NO; + } + *out_data = out; + *out_size = olen; + return GNUNET_OK; +} + + +/** + * 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 `enum TMH_PARSE_JsonNavigationCommand`) + * @return + * #GNUNET_YES if navigation was successful + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error (no response was generated, + * connection must be closed) + */ +int +TMH_PARSE_navigate_json (struct MHD_Connection *connection, + const json_t *root, + ...) +{ + va_list argp; + int ret; + json_t *path; /* what's our current path from 'root'? */ + + path = json_array (); + va_start (argp, root); + ret = 2; /* just not any of the valid return values */ + while (2 == ret) + { + enum TMH_PARSE_JsonNavigationCommand command + = va_arg (argp, + enum TMH_PARSE_JsonNavigationCommand); + + switch (command) + { + case TMH_PARSE_JNC_FIELD: + { + const char *fname = va_arg(argp, const char *); + + json_array_append_new (path, + json_string (fname)); + root = json_object_get (root, + fname); + if (NULL == root) + { + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:s, s:O}", + "error", "missing field in JSON", + "field", fname, + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + } + break; + + case TMH_PARSE_JNC_INDEX: + { + int fnum = va_arg(argp, int); + + json_array_append_new (path, + json_integer (fnum)); + root = json_array_get (root, + fnum); + if (NULL == root) + { + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "missing index in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + } + break; + + case TMH_PARSE_JNC_RET_DATA: + { + void *where = va_arg (argp, void *); + size_t len = va_arg (argp, size_t); + const char *str; + int res; + + str = json_string_value (root); + if (NULL == str) + { + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "string expected", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + res = GNUNET_STRINGS_string_to_data (str, strlen (str), + where, len); + if (GNUNET_OK != res) + { + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed binary data in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + ret = GNUNET_OK; + } + break; + + case TMH_PARSE_JNC_RET_DATA_VAR: + { + void **where = va_arg (argp, void **); + size_t *len = va_arg (argp, size_t *); + const char *str; + int res; + + str = json_string_value (root); + if (NULL == str) + { + ret = (MHD_YES == + TMH_RESPONSE_reply_internal_error (connection, + "json_string_value() failed")) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + *len = (strlen (str) * 5) / 8; + if (NULL != where) + { + *where = GNUNET_malloc (*len); + res = GNUNET_STRINGS_string_to_data (str, + strlen (str), + *where, + *len); + if (GNUNET_OK != res) + { + GNUNET_free (*where); + *where = NULL; + *len = 0; + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed binary data in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + } + ret = GNUNET_OK; + } + break; + + case TMH_PARSE_JNC_RET_TYPED_JSON: + { + int typ = va_arg (argp, int); + const json_t **r_json = va_arg (argp, const json_t **); + + if ( (NULL == root) || + ( (-1 != typ) && + (json_typeof (root) != typ)) ) + { + *r_json = NULL; + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:i, s:i, s:O}", + "error", "wrong JSON field type", + "type_expected", typ, + "type_actual", json_typeof (root), + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + *r_json = root; + json_incref ((json_t *) root); + ret = GNUNET_OK; + } + break; + + case TMH_PARSE_JNC_RET_UINT64: + { + uint64_t *r_u64 = va_arg (argp, uint64_t *); + + if (json_typeof (root) != JSON_INTEGER) + { + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:s, s:i, s:O}", + "error", "wrong JSON field type", + "type_expected", "integer", + "type_actual", json_typeof (root), + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + *r_u64 = (uint64_t) json_integer_value (root); + ret = GNUNET_OK; + } + break; + + case TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY: + { + struct TALER_DenominationPublicKey *where; + size_t len; + const char *str; + int res; + void *buf; + + where = va_arg (argp, + struct TALER_DenominationPublicKey *); + str = json_string_value (root); + if (NULL == str) + { + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "string expected", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + len = (strlen (str) * 5) / 8; + buf = GNUNET_malloc (len); + res = GNUNET_STRINGS_string_to_data (str, + strlen (str), + buf, + len); + if (GNUNET_OK != res) + { + GNUNET_free (buf); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed binary data in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + where->rsa_public_key = GNUNET_CRYPTO_rsa_public_key_decode (buf, + len); + GNUNET_free (buf); + if (NULL == where->rsa_public_key) + { + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed RSA public key in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + ret = GNUNET_OK; + break; + } + + case TMH_PARSE_JNC_RET_RSA_SIGNATURE: + { + struct TALER_DenominationSignature *where; + size_t len; + const char *str; + int res; + void *buf; + + where = va_arg (argp, + struct TALER_DenominationSignature *); + str = json_string_value (root); + if (NULL == str) + { + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "string expected", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + len = (strlen (str) * 5) / 8; + buf = GNUNET_malloc (len); + res = GNUNET_STRINGS_string_to_data (str, + strlen (str), + buf, + len); + if (GNUNET_OK != res) + { + GNUNET_free (buf); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed binary data in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + where->rsa_signature = GNUNET_CRYPTO_rsa_signature_decode (buf, + len); + GNUNET_free (buf); + if (NULL == where->rsa_signature) + { + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed RSA signature in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + ret = GNUNET_OK; + break; + } + + case TMH_PARSE_JNC_RET_AMOUNT: + { + struct TALER_Amount *where = va_arg (argp, void *); + + if (GNUNET_OK != + TALER_json_to_amount ((json_t *) root, + where)) + { + if (MHD_YES != + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "Bad format", + "path", path)) + return GNUNET_SYSERR; + return GNUNET_NO; + } + if (0 != strcmp (where->currency, + TMH_mint_currency_string)) + { + if (MHD_YES != + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O, s:s}", + "error", "Currency not supported", + "path", path, + "currency", where->currency)) + { + memset (where, 0, sizeof (struct TALER_Amount)); + return GNUNET_SYSERR; + } + memset (where, 0, sizeof (struct TALER_Amount)); + return GNUNET_NO; + } + ret = GNUNET_OK; + break; + } + + case TMH_PARSE_JNC_RET_TIME_ABSOLUTE: + { + struct GNUNET_TIME_Absolute *where = va_arg (argp, void *); + + if (GNUNET_OK != + TALER_json_to_abs ((json_t *) root, + where)) + { + if (MHD_YES != + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:s, s:O}", + "error", "Bad format", + "hint", "expected absolute time", + "path", path)) + return GNUNET_SYSERR; + return GNUNET_NO; + } + ret = GNUNET_OK; + break; + } + + default: + GNUNET_break (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_internal_error (connection, + "unhandled value in switch")) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + } + va_end (argp); + json_decref (path); + return ret; +} + + +/** + * Parse JSON object into components based on the given field + * specification. + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param spec field specification for the parser + * @return + * #GNUNET_YES if navigation was successful (caller is responsible + * for freeing allocated variable-size data using + * #TMH_PARSE_release_data() when done) + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error + */ +int +TMH_PARSE_json_data (struct MHD_Connection *connection, + const json_t *root, + struct TMH_PARSE_FieldSpecification *spec) +{ + unsigned int i; + int ret; + + ret = GNUNET_YES; + for (i=0; NULL != spec[i].field_name; i++) + { + if (GNUNET_YES != ret) + break; + switch (spec[i].command) + { + case TMH_PARSE_JNC_FIELD: + GNUNET_break (0); + return GNUNET_SYSERR; + case TMH_PARSE_JNC_INDEX: + GNUNET_break (0); + return GNUNET_SYSERR; + case TMH_PARSE_JNC_RET_DATA: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_DATA, + spec[i].destination, + spec[i].destination_size_in); + break; + case TMH_PARSE_JNC_RET_DATA_VAR: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_DATA_VAR, + (void **) spec[i].destination, + spec[i].destination_size_out); + break; + case TMH_PARSE_JNC_RET_TYPED_JSON: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_TYPED_JSON, + spec[i].type, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_RSA_SIGNATURE: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_RSA_SIGNATURE, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_AMOUNT: + GNUNET_assert (sizeof (struct TALER_Amount) == + spec[i].destination_size_in); + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_AMOUNT, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_TIME_ABSOLUTE: + GNUNET_assert (sizeof (struct GNUNET_TIME_Absolute) == + spec[i].destination_size_in); + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_TIME_ABSOLUTE, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_UINT64: + GNUNET_assert (sizeof (uint64_t) == + spec[i].destination_size_in); + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_UINT64, + spec[i].destination); + break; + } + } + if (GNUNET_YES != ret) + release_data (spec, + i - 1); + return ret; +} + + +/** + * Release all memory allocated for the variable-size fields in + * the parser specification. + * + * @param spec specification to free + */ +void +TMH_PARSE_release_data (struct TMH_PARSE_FieldSpecification *spec) +{ + unsigned int i; + + for (i=0; NULL != spec[i].field_name; i++) ; + release_data (spec, i); +} + + +/** + * Generate line in parser specification for 64-bit integer + * given as an integer in JSON. + * + * @param field name of the field + * @param[out] u64 integer to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_uint64 (const char *field, + uint64_t *u64) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, (void *) u64, sizeof (uint64_t), NULL, TMH_PARSE_JNC_RET_UINT64, 0 }; + return ret; +} + + +/** + * Generate line in parser specification for JSON object value. + * + * @param field name of the field + * @param[out] jsonp address of pointer to JSON to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_object (const char *field, + json_t **jsonp) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, jsonp, 0, NULL, TMH_PARSE_JNC_RET_TYPED_JSON, JSON_OBJECT }; + *jsonp = NULL; + return ret; +} + + +/** + * Generate line in parser specification for JSON array value. + * + * @param field name of the field + * @param[out] jsonp address of JSON pointer to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_array (const char *field, + json_t **jsonp) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, jsonp, 0, NULL, TMH_PARSE_JNC_RET_TYPED_JSON, JSON_ARRAY }; + *jsonp = NULL; + return ret; +} + + +/** + * Generate line in parser specification for an absolute time. + * + * @param field name of the field + * @param[out] atime time to initialize + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_time_abs (const char *field, + struct GNUNET_TIME_Absolute *atime) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, atime, sizeof(struct GNUNET_TIME_Absolute), NULL, TMH_PARSE_JNC_RET_TIME_ABSOLUTE, 0 }; + return ret; +} + + +/** + * Generate line in parser specification for RSA public key. + * + * @param field name of the field + * @param[out] pk key to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_denomination_public_key (const char *field, + struct TALER_DenominationPublicKey *pk) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, pk, 0, NULL, TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY, 0 }; + pk->rsa_public_key = NULL; + return ret; +} + + +/** + * Generate line in parser specification for RSA public key. + * + * @param field name of the field + * @param sig the signature to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_denomination_signature (const char *field, + struct TALER_DenominationSignature *sig) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, sig, 0, NULL, TMH_PARSE_JNC_RET_RSA_SIGNATURE, 0 }; + sig->rsa_signature = NULL; + return ret; +} + + +/** + * Generate line in parser specification for an amount. + * + * @param field name of the field + * @param amount a `struct TALER_Amount *` to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_amount (const char *field, + struct TALER_Amount *amount) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, amount, sizeof(struct TALER_Amount), NULL, TMH_PARSE_JNC_RET_AMOUNT, 0 }; + memset (amount, 0, sizeof (struct TALER_Amount)); + return ret; +} + + +/** + * Generate line in parser specification for variable-size value. + * + * @param field name of the field + * @param[out] ptr pointer to initialize + * @param[out] ptr_size size to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_variable (const char *field, + void **ptr, + size_t *ptr_size) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, ptr, 0, ptr_size, TMH_PARSE_JNC_RET_DATA_VAR, 0 }; + *ptr = NULL; + return ret; +} + +/* end of taler-mint-httpd_parsing.c */ diff --git a/src/backend/taler-mint-httpd_parsing.h b/src/backend/taler-mint-httpd_parsing.h new file mode 100644 index 00000000..a2cf4c46 --- /dev/null +++ b/src/backend/taler-mint-httpd_parsing.h @@ -0,0 +1,408 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_parsing.h + * @brief functions to parse incoming requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_PARSING_H +#define TALER_MINT_HTTPD_PARSING_H + +#include <microhttpd.h> +#include <jansson.h> +#include "taler_util.h" + + +/** + * Process a POST request containing a JSON object. This + * function realizes an MHD POST processor that will + * (incrementally) process JSON data uploaded to the HTTP + * server. It will store the required state in the + * "connection_cls", which must be cleaned up using + * #TMH_PARSE_post_cleanup_callback(). + * + * @param connection the MHD connection + * @param con_cls the closure (points to a `struct Buffer *`) + * @param upload_data the POST data + * @param upload_data_size number of bytes in @a upload_data + * @param json the JSON object for a completed request + * @return + * #GNUNET_YES if json object was parsed or at least + * may be parsed in the future (call again); + * `*json` will be NULL if we need to be called again, + * and non-NULL if we are done. + * #GNUNET_NO is request incomplete or invalid + * (error message was generated) + * #GNUNET_SYSERR on internal error + * (we could not even queue an error message, + * close HTTP session with MHD_NO) + */ +int +TMH_PARSE_post_json (struct MHD_Connection *connection, + void **con_cls, + const char *upload_data, + size_t *upload_data_size, + json_t **json); + + +/** + * Function called whenever we are done with a request + * to clean up our state. + * + * @param con_cls value as it was left by + * #TMH_PARSE_post_json(), to be cleaned up + */ +void +TMH_PARSE_post_cleanup_callback (void *con_cls); + + +/** + * Constants for JSON navigation description. + */ +enum TMH_PARSE_JsonNavigationCommand +{ + /** + * Access a field. + * Param: const char * + */ + TMH_PARSE_JNC_FIELD, + + /** + * Access an array index. + * Param: int + */ + TMH_PARSE_JNC_INDEX, + + /** + * Return base32crockford encoded data of + * constant size. + * Params: (void *, size_t) + */ + TMH_PARSE_JNC_RET_DATA, + + /** + * Return base32crockford encoded data of + * variable size. + * Params: (void **, size_t *) + */ + TMH_PARSE_JNC_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 **) + */ + TMH_PARSE_JNC_RET_TYPED_JSON, + + /** + * Return a `struct GNUNET_CRYPTO_rsa_PublicKey` which was + * encoded as variable-size base32crockford encoded data. + */ + TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY, + + /** + * Return a `struct GNUNET_CRYPTO_rsa_Signature` which was + * encoded as variable-size base32crockford encoded data. + */ + TMH_PARSE_JNC_RET_RSA_SIGNATURE, + + /** + * Return a `struct TALER_Amount` which was + * encoded within its own json object. + */ + TMH_PARSE_JNC_RET_AMOUNT, + + /** + * Return a `struct GNUNET_TIME_Absolute` which was + * encoded within its own json object. + * Param: struct GNUNET_TIME_Absolute * + */ + TMH_PARSE_JNC_RET_TIME_ABSOLUTE, + + /** + * Return a `uint64_t` which was + * encoded as a JSON integer. + * Param: uint64_t * + */ + TMH_PARSE_JNC_RET_UINT64 + +}; + + +/** + * 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 `enum TMH_PARSE_JsonNavigationCommand`) + * @return + * #GNUNET_YES if navigation was successful + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error + */ +int +TMH_PARSE_navigate_json (struct MHD_Connection *connection, + const json_t *root, + ...); + + +/** + * @brief Specification for how to parse a JSON field. + */ +struct TMH_PARSE_FieldSpecification +{ + /** + * Name of the field. NULL only to terminate array. + */ + const char *field_name; + + /** + * Where to store the result. Must have exactly + * @e destination_size bytes, except if @e destination_size is zero. + * NULL to skip assignment (but check presence of the value). + */ + void *destination; + + /** + * How big should the result be, 0 for variable size. In + * this case, @e destination must be a "void **", pointing + * to a location that is currently NULL and is to be allocated. + */ + size_t destination_size_in; + + /** + * @e destination_size_out will then be set to the size of the + * value that was stored in @e destination (useful for + * variable-size allocations). + */ + size_t *destination_size_out; + + /** + * Navigation command to use to extract the value. Note that + * #TMH_PARSE_JNC_RET_DATA or #TMH_PARSE_JNC_RET_DATA_VAR must be used for @e + * destination_size_in and @e destination_size_out to have a + * meaning. #TMH_PARSE_JNC_FIELD and #TMH_PARSE_JNC_INDEX must not be used here! + */ + enum TMH_PARSE_JsonNavigationCommand command; + + /** + * JSON type to use, only meaningful in connection with a @e command + * value of #TMH_PARSE_JNC_RET_TYPED_JSON. Typical values are + * #JSON_ARRAY and #JSON_OBJECT. + */ + int type; + +}; + + +/** + * Parse JSON object into components based on the given field + * specification. + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param spec field specification for the parser + * @return + * #GNUNET_YES if navigation was successful (caller is responsible + * for freeing allocated variable-size data using + * #TMH_PARSE_release_data() when done) + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error + */ +int +TMH_PARSE_json_data (struct MHD_Connection *connection, + const json_t *root, + struct TMH_PARSE_FieldSpecification *spec); + + +/** + * Release all memory allocated for the variable-size fields in + * the parser specification. + * + * @param spec specification to free + */ +void +TMH_PARSE_release_data (struct TMH_PARSE_FieldSpecification *spec); + + +/** + * Generate line in parser specification for fixed-size value. + * + * @param field name of the field + * @param value where to store the value + */ +#define TMH_PARSE_member_fixed(field,value) { field, value, sizeof (*value), NULL, TMH_PARSE_JNC_RET_DATA, 0 } + + +/** + * Generate line in parser specification for variable-size value. + * + * @param field name of the field + * @param[out] ptr pointer to initialize + * @param[out] ptr_size size to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_variable (const char *field, + void **ptr, + size_t *ptr_size); + + +/** + * Generate line in parser specification for 64-bit integer + * given as an integer in JSON. + * + * @param field name of the field + * @param[out] u64 integer to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_uint64 (const char *field, + uint64_t *u64); + + +/** + * Generate line in parser specification for JSON array value. + * + * @param field name of the field + * @param[out] jsonp address of JSON pointer to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_array (const char *field, + json_t **jsonp); + + +/** + * Generate line in parser specification for JSON object value. + * + * @param field name of the field + * @param[out] jsonp address of pointer to JSON to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_object (const char *field, + json_t **jsonp); + + +/** + * Generate line in parser specification for RSA public key. + * + * @param field name of the field + * @param[out] pk key to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_denomination_public_key (const char *field, + struct TALER_DenominationPublicKey *pk); + + +/** + * Generate line in parser specification for RSA public key. + * + * @param field name of the field + * @param sig the signature to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_denomination_signature (const char *field, + struct TALER_DenominationSignature *sig); + + +/** + * Generate line in parser specification for an amount. + * + * @param field name of the field + * @param[out] amount a `struct TALER_Amount *` to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_amount (const char *field, + struct TALER_Amount *amount); + + +/** + * Generate line in parser specification for an absolute time. + * + * @param field name of the field + * @param[out] atime time to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_time_abs (const char *field, + struct GNUNET_TIME_Absolute *atime); + + + +/** + * Generate line in parser specification indicating the end of the spec. + */ +#define TMH_PARSE_MEMBER_END { NULL, NULL, 0, NULL, TMH_PARSE_JNC_FIELD, 0 } + + +/** + * Extraxt fixed-size 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 @a out_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 +TMH_PARSE_mhd_request_arg_data (struct MHD_Connection *connection, + const char *param_name, + void *out_data, + size_t out_size); + + +/** + * Extraxt variable-size base32crockford encoded data from request. + * + * Queues an error response to the connection if the parameter is missing + * or the encoding is invalid. + * + * @param connection the MHD connection + * @param param_name the name of the parameter with the key + * @param[out] out_data pointer to allocate buffer and store the result + * @param[out] out_size set to the size of the buffer allocated in @a out_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 +TMH_PARSE_mhd_request_var_arg_data (struct MHD_Connection *connection, + const char *param_name, + void **out_data, + size_t *out_size); + + + + +#endif /* TALER_MINT_HTTPD_PARSING_H */ diff --git a/src/backend/taler-mint-httpd_refresh.c b/src/backend/taler-mint-httpd_refresh.c new file mode 100644 index 00000000..687fb998 --- /dev/null +++ b/src/backend/taler-mint-httpd_refresh.c @@ -0,0 +1,907 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_refresh.c + * @brief Handle /refresh/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include "taler-mint-httpd_parsing.h" +#include "taler-mint-httpd_mhd.h" +#include "taler-mint-httpd_refresh.h" +#include "taler-mint-httpd_responses.h" +#include "taler-mint-httpd_keystate.h" + + +/** + * Handle a "/refresh/melt" request after the main JSON parsing has happened. + * We now need to validate the coins being melted and the session signature + * and then hand things of to execute the melt operation. + * + * @param connection the MHD connection to handle + * @param num_new_denoms number of coins to be created, size of y-dimension of @a commit_link array + * @param denom_pubs array of @a num_new_denoms keys + * @param coin_count number of coins to be melted, size of y-dimension of @a commit_coin array + * @param coin_melt_details array with @a coin_count entries with melting details + * @param session_hash hash over the data that the client commits to + * @param commit_coin 2d array of coin commitments (what the mint is to sign + * once the "/refres/reveal" of cut and choose is done) + * @param commit_link 2d array of coin link commitments (what the mint is + * to return via "/refresh/link" to enable linkage in the + * future) + * @return MHD result code + */ +static int +handle_refresh_melt_binary (struct MHD_Connection *connection, + unsigned int num_new_denoms, + const struct TALER_DenominationPublicKey *denom_pubs, + unsigned int coin_count, + const struct TMH_DB_MeltDetails *coin_melt_details, + const struct GNUNET_HashCode *session_hash, + struct TALER_MINTDB_RefreshCommitCoin *const* commit_coin, + struct TALER_MINTDB_RefreshCommitLinkP *const* commit_link) +{ + unsigned int i; + struct TMH_KS_StateHandle *key_state; + struct TALER_MINTDB_DenominationKeyInformationP *dki; + struct TALER_Amount cost; + struct TALER_Amount total_cost; + struct TALER_Amount melt; + struct TALER_Amount value; + struct TALER_Amount fee_withdraw; + struct TALER_Amount fee_melt; + struct TALER_Amount total_melt; + + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TMH_mint_currency_string, + &total_cost)); + key_state = TMH_KS_acquire (); + for (i=0;i<num_new_denoms;i++) + { + dki = &TMH_KS_denomination_key_lookup (key_state, + &denom_pubs[i], + TMH_KS_DKU_WITHDRAW)->issue; + TALER_amount_ntoh (&value, + &dki->properties.value); + TALER_amount_ntoh (&fee_withdraw, + &dki->properties.fee_withdraw); + if ( (GNUNET_OK != + TALER_amount_add (&cost, + &value, + &fee_withdraw)) || + (GNUNET_OK != + TALER_amount_add (&total_cost, + &cost, + &total_cost)) ) + { + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_internal_error (connection, + "cost calculation failure"); + } + } + + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (TMH_mint_currency_string, + &total_melt)); + for (i=0;i<coin_count;i++) + { + /* calculate contribution of the i-th melt by subtracting + the fee; add the rest to the total_melt value */ + dki = &TMH_KS_denomination_key_lookup (key_state, + &coin_melt_details[i].coin_info.denom_pub, + TMH_KS_DKU_DEPOSIT)->issue; + TALER_amount_ntoh (&fee_melt, + &dki->properties.fee_refresh); + if (GNUNET_OK != + TALER_amount_subtract (&melt, + &coin_melt_details->melt_amount_with_fee, + &fee_melt)) + { + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_external_error (connection, + "Melt contribution below melting fee"); + } + if (GNUNET_OK != + TALER_amount_add (&total_melt, + &melt, + &total_melt)) + { + TMH_KS_release (key_state); + return TMH_RESPONSE_reply_internal_error (connection, + "balance calculation failure"); + } + } + TMH_KS_release (key_state); + if (0 != + TALER_amount_cmp (&total_cost, + &total_melt)) + { + /* We require total value of coins being melted and + total value of coins being generated to match! */ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s}", + "error", "value mismatch"); + } + return TMH_DB_execute_refresh_melt (connection, + session_hash, + num_new_denoms, + denom_pubs, + coin_count, + coin_melt_details, + commit_coin, + commit_link); +} + + +/** + * Extract public coin information from a JSON object. + * + * @param connection the connection to send error responses to + * @param coin_info the JSON object to extract the coin info from + * @param[out] r_melt_detail set to details about the coin's melting permission (if valid) + * @return #GNUNET_YES if coin public info in JSON was valid + * #GNUNET_NO JSON was invalid, response was generated + * #GNUNET_SYSERR on internal error + */ +static int +get_coin_public_info (struct MHD_Connection *connection, + json_t *coin_info, + struct TMH_DB_MeltDetails *r_melt_detail) +{ + int ret; + struct TALER_CoinSpendSignatureP melt_sig; + struct TALER_DenominationSignature sig; + struct TALER_DenominationPublicKey pk; + struct TALER_Amount amount; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_fixed ("coin_pub", &r_melt_detail->coin_info.coin_pub), + TMH_PARSE_member_denomination_signature ("denom_sig", &sig), + TMH_PARSE_member_denomination_public_key ("denom_pub", &pk), + TMH_PARSE_member_fixed ("confirm_sig", &melt_sig), + TMH_PARSE_member_amount ("value_with_fee", &amount), + TMH_PARSE_MEMBER_END + }; + + ret = TMH_PARSE_json_data (connection, + coin_info, + spec); + if (GNUNET_OK != ret) + return ret; + /* check mint signature on the coin */ + r_melt_detail->coin_info.denom_sig = sig; + r_melt_detail->coin_info.denom_pub = pk; + if (GNUNET_OK != + TALER_test_coin_valid (&r_melt_detail->coin_info)) + { + TMH_PARSE_release_data (spec); + r_melt_detail->coin_info.denom_sig.rsa_signature = NULL; + r_melt_detail->coin_info.denom_pub.rsa_public_key = NULL; + return (MHD_YES == + TMH_RESPONSE_reply_signature_invalid (connection, + "denom_sig")) + ? GNUNET_NO : GNUNET_SYSERR; + } + r_melt_detail->melt_sig = melt_sig; + r_melt_detail->melt_amount_with_fee = amount; + TMH_PARSE_release_data (spec); + return GNUNET_OK; +} + + +/** + * Verify that the signature shows that this coin is to be melted into + * the given @a session_pub melting session, and that this is a valid + * coin (we know the denomination key and the signature on it is + * valid). Essentially, this does all of the per-coin checks that can + * be done before the transaction starts. + * + * @param connection the connection to send error responses to + * @param session_hash hash over refresh session the coin is melted into + * @param melt_detail details about the coin's melting permission (if valid) + * @return #GNUNET_YES if coin public info in JSON was valid + * #GNUNET_NO JSON was invalid, response was generated + * #GNUNET_SYSERR on internal error + */ +static int +verify_coin_public_info (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + const struct TMH_DB_MeltDetails *melt_detail) +{ + struct TALER_RefreshMeltCoinAffirmationPS body; + struct TMH_KS_StateHandle *key_state; + struct TALER_MINTDB_DenominationKeyIssueInformation *dki; + struct TALER_Amount fee_refresh; + + key_state = TMH_KS_acquire (); + dki = TMH_KS_denomination_key_lookup (key_state, + &melt_detail->coin_info.denom_pub, + TMH_KS_DKU_DEPOSIT); + if (NULL == dki) + { + TMH_KS_release (key_state); + TALER_LOG_WARNING ("Unknown denomination key in /refresh/melt request\n"); + return TMH_RESPONSE_reply_arg_unknown (connection, + "denom_pub"); + } + /* FIXME: need to check if denomination key is still + valid for issuing! (#3634) */ + TALER_amount_ntoh (&fee_refresh, + &dki->issue.properties.fee_refresh); + body.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS)); + body.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); + body.session_hash = *session_hash; + TALER_amount_hton (&body.amount_with_fee, + &melt_detail->melt_amount_with_fee); + TALER_amount_hton (&body.melt_fee, + &fee_refresh); + body.coin_pub = melt_detail->coin_info.coin_pub; + if (TALER_amount_cmp (&fee_refresh, + &melt_detail->melt_amount_with_fee) < 0) + { + TMH_KS_release (key_state); + return (MHD_YES == + TMH_RESPONSE_reply_external_error (connection, + "melt amount smaller than melting fee")) + ? GNUNET_NO : GNUNET_SYSERR; + } + + TMH_KS_release (key_state); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, + &body.purpose, + &melt_detail->melt_sig.eddsa_signature, + &melt_detail->coin_info.coin_pub.eddsa_pub)) + { + if (MHD_YES != + TMH_RESPONSE_reply_signature_invalid (connection, + "confirm_sig")) + return GNUNET_SYSERR; + return GNUNET_NO; + } + return GNUNET_OK; +} + + +/** + * Release memory from the @a commit_coin array. + * + * @param commit_coin array to release + * @param kappa size of 1st dimension + * @param num_new_coins size of 2nd dimension + */ +static void +free_commit_coins (struct TALER_MINTDB_RefreshCommitCoin **commit_coin, + unsigned int kappa, + unsigned int num_new_coins) +{ + unsigned int i; + unsigned int j; + + for (i=0;i<kappa;i++) + { + if (NULL == commit_coin[i]) + break; + for (j=0;j<num_new_coins;j++) + { + GNUNET_free_non_null (commit_coin[i][j].coin_ev); + GNUNET_free_non_null (commit_coin[i][j].refresh_link); + } + GNUNET_free (commit_coin[i]); + } +} + + +/** + * Release memory from the @a commit_link array. + * + * @param commit_link array to release + * @param kappa size of 1st dimension + * @param num_old_coins size of 2nd dimension + */ +static void +free_commit_links (struct TALER_MINTDB_RefreshCommitLinkP **commit_link, + unsigned int kappa, + unsigned int num_old_coins) +{ + unsigned int i; + + for (i=0;i<kappa;i++) + { + if (NULL == commit_link[i]) + break; + GNUNET_free (commit_link[i]); + } +} + + +/** + * Handle a "/refresh/melt" request after the first parsing has happened. + * We now need to validate the coins being melted and the session signature + * and then hand things of to execute the melt operation. This function + * parses the JSON arrays and then passes processing on to + * #handle_refresh_melt_binary(). + * + * @param connection the MHD connection to handle + * @param new_denoms array of denomination keys + * @param melt_coins array of coins to melt + * @param num_oldcoins number of coins that are being melted + * @param transfer_pubs #TALER_CNC_KAPPA-dimensional array of @a num_oldcoins transfer keys + * @param secret_encs #TALER_CNC_KAPPA-dimensional array of @a num_oldcoins secrets + * @param num_newcoins number of coins that the refresh will generate + * @param coin_evs #TALER_CNC_KAPPA-dimensional array of @a num_newcoins envelopes to sign + * @param link_encs #TALER_CNC_KAPPA-dimensional array of @a num_newcoins encrypted links + * @return MHD result code + */ +static int +handle_refresh_melt_json (struct MHD_Connection *connection, + const json_t *new_denoms, + const json_t *melt_coins, + unsigned int num_oldcoins, + const json_t *transfer_pubs, + const json_t *secret_encs, + unsigned int num_newcoins, + const json_t *coin_evs, + const json_t *link_encs) + +{ + int res; + unsigned int i; + unsigned int j; + struct TALER_DenominationPublicKey *denom_pubs; + unsigned int num_new_denoms; + struct TMH_DB_MeltDetails *coin_melt_details; + unsigned int coin_count; + struct GNUNET_HashCode session_hash; + struct GNUNET_HashContext *hash_context; + struct TALER_MINTDB_RefreshCommitCoin *commit_coin[TALER_CNC_KAPPA]; + struct TALER_MINTDB_RefreshCommitLinkP *commit_link[TALER_CNC_KAPPA]; + + /* For the signature check, we hash most of the inputs together + (except for the signatures on the coins). */ + hash_context = GNUNET_CRYPTO_hash_context_start (); + num_new_denoms = json_array_size (new_denoms); + denom_pubs = GNUNET_malloc (num_new_denoms * + sizeof (struct TALER_DenominationPublicKey)); + for (i=0;i<num_new_denoms;i++) + { + char *buf; + size_t buf_size; + + res = TMH_PARSE_navigate_json (connection, + new_denoms, + TMH_PARSE_JNC_INDEX, (int) i, + TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY, + &denom_pubs[i].rsa_public_key); + if (GNUNET_OK != res) + { + for (j=0;j<i;j++) + GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key); + GNUNET_free (denom_pubs); + return res; + } + buf_size = GNUNET_CRYPTO_rsa_public_key_encode (denom_pubs[i].rsa_public_key, + &buf); + GNUNET_CRYPTO_hash_context_read (hash_context, + buf, + buf_size); + GNUNET_free (buf); + } + + coin_count = json_array_size (melt_coins); + coin_melt_details = GNUNET_malloc (coin_count * + sizeof (struct TMH_DB_MeltDetails)); + for (i=0;i<coin_count;i++) + { + /* decode JSON data on coin to melt */ + struct TALER_AmountNBO melt_amount; + + res = get_coin_public_info (connection, + json_array_get (melt_coins, i), + &coin_melt_details[i]); + if (GNUNET_OK != res) + { + for (j=0;j<i;j++) + { + GNUNET_CRYPTO_rsa_public_key_free (coin_melt_details[j].coin_info.denom_pub.rsa_public_key); + GNUNET_CRYPTO_rsa_signature_free (coin_melt_details[j].coin_info.denom_sig.rsa_signature); + } + for (j=0;j<num_new_denoms;j++) + GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key); + GNUNET_free (coin_melt_details); + GNUNET_free (denom_pubs); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + /* Check that the client does not try to melt the same coin twice + into the same session! */ + for (j=0;j<i;j++) + { + if (0 == memcmp (&coin_melt_details[i].coin_info.coin_pub, + &coin_melt_details[j].coin_info.coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP))) + { + for (j=0;j<i;j++) + { + GNUNET_CRYPTO_rsa_public_key_free (coin_melt_details[j].coin_info.denom_pub.rsa_public_key); + GNUNET_CRYPTO_rsa_signature_free (coin_melt_details[j].coin_info.denom_sig.rsa_signature); + } + for (j=0;j<num_new_denoms;j++) + GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key); + GNUNET_free (coin_melt_details); + GNUNET_free (denom_pubs); + return TMH_RESPONSE_reply_external_error (connection, + "melting same coin twice in same session is not allowed"); + } + } + TALER_amount_hton (&melt_amount, + &coin_melt_details[i].melt_amount_with_fee); + GNUNET_CRYPTO_hash_context_read (hash_context, + &coin_melt_details[i].coin_info.coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP)); + GNUNET_CRYPTO_hash_context_read (hash_context, + &melt_amount, + sizeof (struct TALER_AmountNBO)); + + } + + /* parse JSON arrays into 2d binary arrays and hash everything + together for the signature check */ + memset (commit_coin, 0, sizeof (commit_coin)); + memset (commit_link, 0, sizeof (commit_link)); + for (i = 0; i < TALER_CNC_KAPPA; i++) + { + commit_coin[i] = GNUNET_malloc (num_newcoins * + sizeof (struct TALER_MINTDB_RefreshCommitCoin)); + for (j = 0; j < num_newcoins; j++) + { + char *link_enc; + size_t link_enc_size; + struct TALER_MINTDB_RefreshCommitCoin *rcc = &commit_coin[i][j]; + + res = TMH_PARSE_navigate_json (connection, + coin_evs, + TMH_PARSE_JNC_INDEX, (int) i, + TMH_PARSE_JNC_INDEX, (int) j, + TMH_PARSE_JNC_RET_DATA_VAR, + &rcc->coin_ev, + &rcc->coin_ev_size); + + if (GNUNET_OK != res) + { + GNUNET_CRYPTO_hash_context_abort (hash_context); + free_commit_coins (commit_coin, + TALER_CNC_KAPPA, + num_newcoins); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + GNUNET_CRYPTO_hash_context_read (hash_context, + rcc->coin_ev, + rcc->coin_ev_size); + res = TMH_PARSE_navigate_json (connection, + link_encs, + TMH_PARSE_JNC_INDEX, (int) i, + TMH_PARSE_JNC_INDEX, (int) j, + TMH_PARSE_JNC_RET_DATA_VAR, + &link_enc, + &link_enc_size); + if (GNUNET_OK != res) + { + GNUNET_CRYPTO_hash_context_abort (hash_context); + free_commit_coins (commit_coin, + TALER_CNC_KAPPA, + num_newcoins); + GNUNET_free (link_enc); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + rcc->refresh_link + = TALER_refresh_link_encrypted_decode (link_enc, + link_enc_size); + GNUNET_CRYPTO_hash_context_read (hash_context, + link_enc, + link_enc_size); + GNUNET_free (link_enc); + } + } + + for (i = 0; i < TALER_CNC_KAPPA; i++) + { + commit_link[i] = GNUNET_malloc (num_oldcoins * + sizeof (struct TALER_MINTDB_RefreshCommitLinkP)); + for (j = 0; j < num_oldcoins; j++) + { + struct TALER_MINTDB_RefreshCommitLinkP *rcl = &commit_link[i][j]; + + res = TMH_PARSE_navigate_json (connection, + transfer_pubs, + TMH_PARSE_JNC_INDEX, (int) i, + TMH_PARSE_JNC_INDEX, (int) j, + TMH_PARSE_JNC_RET_DATA, + &rcl->transfer_pub, + sizeof (struct TALER_TransferPublicKeyP)); + + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + GNUNET_CRYPTO_hash_context_abort (hash_context); + free_commit_coins (commit_coin, + TALER_CNC_KAPPA, + num_newcoins); + free_commit_links (commit_link, + TALER_CNC_KAPPA, + num_oldcoins); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + res = TMH_PARSE_navigate_json (connection, + secret_encs, + TMH_PARSE_JNC_INDEX, (int) i, + TMH_PARSE_JNC_INDEX, (int) j, + TMH_PARSE_JNC_RET_DATA, + &rcl->shared_secret_enc, + sizeof (struct TALER_EncryptedLinkSecretP)); + + if (GNUNET_OK != res) + { + GNUNET_break (GNUNET_SYSERR != res); + GNUNET_CRYPTO_hash_context_abort (hash_context); + free_commit_coins (commit_coin, + TALER_CNC_KAPPA, + num_newcoins); + free_commit_links (commit_link, + TALER_CNC_KAPPA, + num_oldcoins); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + GNUNET_CRYPTO_hash_context_read (hash_context, + rcl, + sizeof (struct TALER_MINTDB_RefreshCommitLinkP)); + } + + } + GNUNET_CRYPTO_hash_context_finish (hash_context, + &session_hash); + + for (i=0;i<coin_count;i++) + { + /* verify signatures on coins to melt */ + res = verify_coin_public_info (connection, + &session_hash, + &coin_melt_details[i]); + if (GNUNET_OK != res) + { + res = (GNUNET_NO == res) ? MHD_YES : MHD_NO; + goto cleanup; + } + } + + /* execute commit */ + res = handle_refresh_melt_binary (connection, + num_new_denoms, + denom_pubs, + coin_count, + coin_melt_details, + &session_hash, + commit_coin, + commit_link); + cleanup: + free_commit_coins (commit_coin, + TALER_CNC_KAPPA, + num_newcoins); + free_commit_links (commit_link, + TALER_CNC_KAPPA, + num_oldcoins); + for (j=0;j<coin_count;j++) + { + GNUNET_CRYPTO_rsa_public_key_free (coin_melt_details[j].coin_info.denom_pub.rsa_public_key); + GNUNET_CRYPTO_rsa_signature_free (coin_melt_details[j].coin_info.denom_sig.rsa_signature); + } + for (j=0;j<num_new_denoms;j++) + GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key); + GNUNET_free (coin_melt_details); + GNUNET_free (denom_pubs); + return res; +} + + +/** + * Handle a "/refresh/melt" request. Parses the request into the JSON + * components and then hands things of to #handle_refresh_melt_json() + * to validate the melted coins, the signature and execute the melt + * using TMH_DB_execute_refresh_melt(). + * + * @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 +TMH_REFRESH_handler_refresh_melt (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + json_t *root; + json_t *new_denoms; + json_t *melt_coins; + json_t *coin_evs; + json_t *link_encs; + json_t *transfer_pubs; + json_t *secret_encs; + unsigned int num_oldcoins; + unsigned int num_newcoins; + json_t *coin_detail; + int res; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_array ("new_denoms", &new_denoms), + TMH_PARSE_member_array ("melt_coins", &melt_coins), + TMH_PARSE_member_array ("coin_evs", &coin_evs), + TMH_PARSE_member_array ("link_encs", &link_encs), + TMH_PARSE_member_array ("transfer_pubs", &transfer_pubs), + TMH_PARSE_member_array ("secret_encs", &secret_encs), + TMH_PARSE_MEMBER_END + }; + + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + return MHD_NO; + if ( (GNUNET_NO == res) || (NULL == root) ) + return MHD_YES; + + res = TMH_PARSE_json_data (connection, + root, + spec); + json_decref (root); + if (GNUNET_OK != res) + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + + /* Determine dimensionality of the request (kappa, #old and #new coins) */ + if (TALER_CNC_KAPPA != json_array_size (coin_evs)) + { + GNUNET_break_op (0); + TMH_PARSE_release_data (spec); + return TMH_RESPONSE_reply_arg_invalid (connection, + "coin_evs"); + } + if (TALER_CNC_KAPPA != json_array_size (transfer_pubs)) + { + GNUNET_break_op (0); + TMH_PARSE_release_data (spec); + return TMH_RESPONSE_reply_arg_invalid (connection, + "transfer_pubs"); + } + res = TMH_PARSE_navigate_json (connection, coin_evs, + TMH_PARSE_JNC_INDEX, (int) 0, + TMH_PARSE_JNC_RET_DATA, + JSON_ARRAY, &coin_detail); + if (GNUNET_OK != res) + { + TMH_PARSE_release_data (spec); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + num_newcoins = json_array_size (coin_detail); + res = TMH_PARSE_navigate_json (connection, + transfer_pubs, + TMH_PARSE_JNC_INDEX, (int) 0, + TMH_PARSE_JNC_RET_DATA, + JSON_ARRAY, &coin_detail); + if (GNUNET_OK != res) + { + TMH_PARSE_release_data (spec); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + num_oldcoins = json_array_size (coin_detail); + + res = handle_refresh_melt_json (connection, + new_denoms, + melt_coins, + num_oldcoins, + transfer_pubs, + secret_encs, + num_newcoins, + coin_evs, + link_encs); + + TMH_PARSE_release_data (spec); + return res; +} + + +/** + * Handle a "/refresh/reveal" request. Parses the given JSON + * transfer private keys and if successful, passes everything to + * #TMH_DB_execute_refresh_reveal() which will verify that the + * revealed information is valid then returns the signed refreshed + * coins. + * + * @param connection the MHD connection to handle + * @param session_hash hash identifying the melting session + * @param num_oldcoins length of the 2nd dimension of @a transfer_privs array + * @param tp_json private transfer keys in JSON format + * @return MHD result code + */ +static int +handle_refresh_reveal_json (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + unsigned int num_oldcoins, + const json_t *tp_json) +{ + struct TALER_TransferPrivateKeyP *transfer_privs[TALER_CNC_KAPPA - 1]; + unsigned int i; + unsigned int j; + int res; + + for (i = 0; i < TALER_CNC_KAPPA - 1; i++) + transfer_privs[i] = GNUNET_malloc (num_oldcoins * + sizeof (struct TALER_TransferPrivateKeyP)); + res = GNUNET_OK; + for (i = 0; i < TALER_CNC_KAPPA - 1; i++) + { + if (GNUNET_OK != res) + break; + for (j = 0; j < num_oldcoins; j++) + { + if (GNUNET_OK != res) + break; + res = TMH_PARSE_navigate_json (connection, + tp_json, + TMH_PARSE_JNC_INDEX, (int) i, + TMH_PARSE_JNC_INDEX, (int) j, + TMH_PARSE_JNC_RET_DATA, + &transfer_privs[i][j], + sizeof (struct TALER_TransferPrivateKeyP)); + } + } + if (GNUNET_OK != res) + res = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + else + res = TMH_DB_execute_refresh_reveal (connection, + session_hash, + num_oldcoins, + transfer_privs); + for (i = 0; i < TALER_CNC_KAPPA - 1; i++) + GNUNET_free (transfer_privs[i]); + return res; +} + + +/** + * Handle a "/refresh/reveal" request. This time, the client reveals + * the private transfer keys except for the cut-and-choose value + * returned from "/refresh/melt". This function parses the revealed + * keys and secrets and ultimately passes everything to + * #TMH_DB_execute_refresh_reveal() which will verify that the + * revealed information is valid then returns the signed refreshed + * coins. + * + * @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 +TMH_REFRESH_handler_refresh_reveal (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct GNUNET_HashCode session_hash; + int res; + unsigned int num_oldcoins; + json_t *reveal_detail; + json_t *root; + json_t *transfer_privs; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_fixed ("session_hash", &session_hash), + TMH_PARSE_member_array ("transfer_privs", &transfer_privs), + TMH_PARSE_MEMBER_END + }; + + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + return MHD_NO; + if ( (GNUNET_NO == res) || (NULL == root) ) + return MHD_YES; + + res = TMH_PARSE_json_data (connection, + root, + spec); + json_decref (root); + if (GNUNET_OK != res) + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + + /* Determine dimensionality of the request (kappa and #old coins) */ + /* Note we do +1 as 1 row (cut-and-choose!) is missing! */ + if (TALER_CNC_KAPPA != json_array_size (transfer_privs) + 1) + { + TMH_PARSE_release_data (spec); + return TMH_RESPONSE_reply_arg_invalid (connection, + "transfer_privs"); + } + res = TMH_PARSE_navigate_json (connection, + transfer_privs, + TMH_PARSE_JNC_INDEX, 0, + TMH_PARSE_JNC_RET_TYPED_JSON, + JSON_ARRAY, + &reveal_detail); + if (GNUNET_OK != res) + { + TMH_PARSE_release_data (spec); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + num_oldcoins = json_array_size (reveal_detail); + res = handle_refresh_reveal_json (connection, + &session_hash, + num_oldcoins, + transfer_privs); + TMH_PARSE_release_data (spec); + return res; +} + + +/** + * Handle a "/refresh/link" request. Note that for "/refresh/link" + * we do use a simple HTTP GET, and a HTTP POST! + * + * @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 +TMH_REFRESH_handler_refresh_link (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct TALER_CoinSpendPublicKeyP coin_pub; + int res; + + res = TMH_PARSE_mhd_request_arg_data (connection, + "coin_pub", + &coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP)); + if (GNUNET_SYSERR == res) + return MHD_NO; + if (GNUNET_OK != res) + return MHD_YES; + return TMH_DB_execute_refresh_link (connection, + &coin_pub); +} + + +/* end of taler-mint-httpd_refresh.c */ diff --git a/src/backend/taler-mint-httpd_refresh.h b/src/backend/taler-mint-httpd_refresh.h new file mode 100644 index 00000000..8fe12a27 --- /dev/null +++ b/src/backend/taler-mint-httpd_refresh.h @@ -0,0 +1,94 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_refresh.h + * @brief Handle /refresh/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_REFRESH_H +#define TALER_MINT_HTTPD_REFRESH_H + +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + + +/** + * Handle a "/refresh/melt" request. Parses the request into the JSON + * components and then hands things of to #handle_refresh_melt_json() + * to validate the melted coins, the signature and execute the melt + * using TMH_DB_execute_refresh_melt(). + * + * @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 +TMH_REFRESH_handler_refresh_melt (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Handle a "/refresh/reveal" request. This time, the client reveals + * the private transfer keys except for the cut-and-choose value + * returned from "/refresh/commit". This function parses the revealed + * keys and secrets and ultimately passes everything to + * #TMH_DB_execute_refresh_reveal() which will verify that the + * revealed information is valid then returns the signed refreshed + * coins. + * + * @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 +TMH_REFRESH_handler_refresh_reveal (struct TMH_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 +TMH_REFRESH_handler_refresh_link (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +#endif diff --git a/src/backend/taler-mint-httpd_responses.c b/src/backend/taler-mint-httpd_responses.c new file mode 100644 index 00000000..57b233e7 --- /dev/null +++ b/src/backend/taler-mint-httpd_responses.c @@ -0,0 +1,997 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_responses.c + * @brief API for generating the various replies of the mint; these + * functions are called TMH_RESPONSE_reply_ and they generate + * and queue MHD response objects for a given connection. + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler-mint-httpd_responses.h" +#include "taler_util.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler-mint-httpd_keystate.h" + + +/** + * Send JSON object as response. + * + * @param connection the MHD connection + * @param json the json object + * @param response_code the http response code + * @return MHD result code + */ +int +TMH_RESPONSE_reply_json (struct MHD_Connection *connection, + const json_t *json, + unsigned int response_code) +{ + struct MHD_Response *resp; + char *json_str; + int ret; + + json_str = json_dumps (json, JSON_INDENT(2)); + GNUNET_assert (NULL != json_str); + resp = MHD_create_response_from_buffer (strlen (json_str), json_str, + MHD_RESPMEM_MUST_FREE); + if (NULL == resp) + { + free (json_str); + GNUNET_break (0); + return MHD_NO; + } + (void) MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_TYPE, + "application/json"); + ret = MHD_queue_response (connection, + response_code, + resp); + MHD_destroy_response (resp); + return ret; +} + + +/** + * Function to call to handle the request by building a JSON + * reply from a format string and varargs. + * + * @param connection the MHD connection to handle + * @param response_code HTTP response code to use + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TMH_RESPONSE_reply_json_pack (struct MHD_Connection *connection, + unsigned int response_code, + const char *fmt, + ...) +{ + json_t *json; + va_list argp; + int ret; + json_error_t jerror; + + va_start (argp, fmt); + json = json_vpack_ex (&jerror, 0, fmt, argp); + va_end (argp); + if (NULL == json) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to pack JSON with format `%s': %s\n", + fmt, + jerror.text); + GNUNET_break (0); + return MHD_NO; + } + ret = TMH_RESPONSE_reply_json (connection, + json, + response_code); + json_decref (json); + return ret; +} + + +/** + * Send a response indicating an invalid argument. + * + * @param connection the MHD connection to use + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_arg_invalid (struct MHD_Connection *connection, + const char *param_name) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:s}", + "error", "invalid parameter", + "parameter", param_name); +} + + +/** + * Send a response indicating an argument refering to a + * resource unknown to the mint (i.e. unknown reserve or + * denomination key). + * + * @param connection the MHD connection to use + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_arg_unknown (struct MHD_Connection *connection, + const char *param_name) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s, s:s}", + "error", "unknown entity referenced", + "parameter", param_name); +} + + +/** + * Send a response indicating an invalid signature. + * + * @param connection the MHD connection to use + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_signature_invalid (struct MHD_Connection *connection, + const char *param_name) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_UNAUTHORIZED, + "{s:s, s:s}", + "error", "invalid signature", + "parameter", param_name); +} + + +/** + * Send a response indicating a missing argument. + * + * @param connection the MHD connection to use + * @param param_name the parameter that is missing + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_arg_missing (struct MHD_Connection *connection, + const char *param_name) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{ s:s, s:s}", + "error", "missing parameter", + "parameter", param_name); +} + + +/** + * Send a response indicating permission denied. + * + * @param connection the MHD connection to use + * @param hint hint about why access was denied + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_permission_denied (struct MHD_Connection *connection, + const char *hint) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_FORBIDDEN, + "{s:s, s:s}", + "error", "permission denied", + "hint", hint); +} + + +/** + * Send a response indicating an internal error. + * + * @param connection the MHD connection to use + * @param hint hint about the internal error's nature + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, + const char *hint) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + "{s:s, s:s}", + "error", "internal error", + "hint", hint); +} + + +/** + * Send a response indicating an external error. + * + * @param connection the MHD connection to use + * @param hint hint about the error's nature + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, + const char *hint) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:s}", + "error", "client error", + "hint", hint); +} + + +/** + * Send a response indicating an error committing a + * transaction (concurrent interference). + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_commit_error (struct MHD_Connection *connection) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s}", + "error", "commit failure"); +} + + +/** + * Send a response indicating a failure to talk to the Mint's + * database. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_internal_db_error (struct MHD_Connection *connection) +{ + return TMH_RESPONSE_reply_internal_error (connection, + "Failed to connect to database"); +} + + +/** + * Send a response indicating that the request was too big. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_request_too_large (struct MHD_Connection *connection) +{ + struct MHD_Response *resp; + int ret; + + resp = MHD_create_response_from_buffer (0, + NULL, + MHD_RESPMEM_PERSISTENT); + if (NULL == resp) + return MHD_NO; + ret = MHD_queue_response (connection, + MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, + resp); + MHD_destroy_response (resp); + return ret; +} + + +/** + * Send a response indicating that the JSON was malformed. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s}", + "error", + "invalid json"); +} + + +/** + * Send confirmation of deposit success to client. This function + * will create a signed message affirming the given information + * and return it to the client. By this, the mint affirms that + * the coin had sufficient (residual) value for the specified + * transaction and that it will execute the requested deposit + * operation with the given wiring details. + * + * @param connection connection to the client + * @param coin_pub public key of the coin + * @param h_wire hash of wire details + * @param h_contract hash of contract details + * @param transaction_id transaction ID + * @param timestamp client's timestamp + * @param refund_deadline until when this deposit be refunded + * @param merchant merchant public key + * @param amount_without_fee fraction of coin value to deposit, without the fee + * @return MHD result code + */ +int +TMH_RESPONSE_reply_deposit_success (struct MHD_Connection *connection, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + uint64_t transaction_id, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund_deadline, + const struct TALER_MerchantPublicKeyP *merchant, + const struct TALER_Amount *amount_without_fee) +{ + struct TALER_DepositConfirmationPS dc; + struct TALER_MintPublicKeyP pub; + struct TALER_MintSignatureP sig; + + dc.purpose.purpose = htonl (TALER_SIGNATURE_MINT_CONFIRM_DEPOSIT); + dc.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)); + dc.h_contract = *h_contract; + dc.h_wire = *h_wire; + dc.transaction_id = GNUNET_htonll (transaction_id); + dc.timestamp = GNUNET_TIME_absolute_hton (timestamp); + dc.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); + TALER_amount_hton (&dc.amount_without_fee, + amount_without_fee); + dc.coin_pub = *coin_pub; + dc.merchant = *merchant; + TMH_KS_sign (&dc.purpose, + &pub, + &sig); + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:s, s:o, s:o}", + "status", "DEPOSIT_OK", + "sig", TALER_json_from_data (&sig, + sizeof (sig)), + "pub", TALER_json_from_data (&pub, + sizeof (pub))); +} + + +/** + * Compile the transaction history of a coin into a JSON object. + * + * @param tl transaction history to JSON-ify + * @return json representation of the @a rh + */ +static json_t * +compile_transaction_history (const struct TALER_MINTDB_TransactionList *tl) +{ + json_t *transaction; + const char *type; + struct TALER_Amount value; + json_t *history; + const struct TALER_MINTDB_TransactionList *pos; + + history = json_array (); + for (pos = tl; NULL != pos; pos = pos->next) + { + switch (pos->type) + { + case TALER_MINTDB_TT_DEPOSIT: + { + struct TALER_DepositRequestPS dr; + const struct TALER_MINTDB_Deposit *deposit = pos->details.deposit; + + type = "deposit"; + value = deposit->amount_with_fee; + dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); + dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS)); + dr.h_contract = deposit->h_contract; + dr.h_wire = deposit->h_wire; + dr.timestamp = GNUNET_TIME_absolute_hton (deposit->timestamp); + dr.refund_deadline = GNUNET_TIME_absolute_hton (deposit->refund_deadline); + dr.transaction_id = GNUNET_htonll (deposit->transaction_id); + TALER_amount_hton (&dr.amount_with_fee, + &deposit->amount_with_fee); + TALER_amount_hton (&dr.deposit_fee, + &deposit->deposit_fee); + dr.merchant = deposit->merchant_pub; + dr.coin_pub = deposit->coin.coin_pub; + transaction = TALER_json_from_eddsa_sig (&dr.purpose, + &deposit->csig.eddsa_signature); + break; + } + case TALER_MINTDB_TT_REFRESH_MELT: + { + struct TALER_RefreshMeltCoinAffirmationPS ms; + const struct TALER_MINTDB_RefreshMelt *melt = pos->details.melt; + + type = "melt"; + value = melt->amount_with_fee; + ms.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); + ms.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS)); + ms.session_hash = melt->session_hash; + TALER_amount_hton (&ms.amount_with_fee, + &melt->amount_with_fee); + TALER_amount_hton (&ms.melt_fee, + &melt->melt_fee); + ms.coin_pub = melt->coin.coin_pub; + transaction = TALER_json_from_eddsa_sig (&ms.purpose, + &melt->coin_sig.eddsa_signature); + } + break; + case TALER_MINTDB_TT_LOCK: + { + type = "lock"; + value = pos->details.lock->amount; + transaction = NULL; + GNUNET_break (0); /* #3625: Lock NOT implemented! */ + break; + } + default: + GNUNET_assert (0); + } + json_array_append_new (history, + json_pack ("{s:s, s:o, s:o}", + "type", type, + "amount", TALER_json_from_amount (&value), + "signature", transaction)); + } + return history; +} + + +/** + * Send proof that a /deposit request is invalid to client. This + * function will create a message with all of the operations affecting + * the coin that demonstrate that the coin has insufficient value. + * + * @param connection connection to the client + * @param tl transaction list to use to build reply + * @return MHD result code + */ +int +TMH_RESPONSE_reply_deposit_insufficient_funds (struct MHD_Connection *connection, + const struct TALER_MINTDB_TransactionList *tl) +{ + json_t *history; + + history = compile_transaction_history (tl); + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_FORBIDDEN, + "{s:s, s:o}", + "error", "insufficient funds", + "history", history); +} + + +/** + * Compile the history of a reserve into a JSON object + * and calculate the total balance. + * + * @param rh reserve history to JSON-ify + * @param[out] balance set to current reserve balance + * @return json representation of the @a rh, NULL on error + */ +static json_t * +compile_reserve_history (const struct TALER_MINTDB_ReserveHistory *rh, + struct TALER_Amount *balance) +{ + struct TALER_Amount deposit_total; + struct TALER_Amount withdraw_total; + struct TALER_Amount value; + json_t *json_history; + json_t *transaction; + int ret; + const struct TALER_MINTDB_ReserveHistory *pos; + struct TALER_WithdrawRequestPS wr; + + json_history = json_array (); + ret = 0; + for (pos = rh; NULL != pos; pos = pos->next) + { + switch (pos->type) + { + case TALER_MINTDB_RO_BANK_TO_MINT: + if (0 == ret) + deposit_total = pos->details.bank->amount; + else + if (GNUNET_OK != + TALER_amount_add (&deposit_total, + &deposit_total, + &pos->details.bank->amount)) + { + json_decref (json_history); + return NULL; + } + ret = 1; + json_array_append_new (json_history, + json_pack ("{s:s, s:O, s:o}", + "type", "DEPOSIT", + "wire", pos->details.bank->wire, + "amount", TALER_json_from_amount (&pos->details.bank->amount))); + break; + case TALER_MINTDB_RO_WITHDRAW_COIN: + break; + } + } + + ret = 0; + for (pos = rh; NULL != pos; pos = pos->next) + { + switch (pos->type) + { + case TALER_MINTDB_RO_BANK_TO_MINT: + break; + case TALER_MINTDB_RO_WITHDRAW_COIN: + value = pos->details.withdraw->amount_with_fee; + if (0 == ret) + { + withdraw_total = value; + } + else + { + if (GNUNET_OK != + TALER_amount_add (&withdraw_total, + &withdraw_total, + &value)) + { + json_decref (json_history); + return NULL; + } + } + ret = 1; + wr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); + wr.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS)); + wr.reserve_pub = pos->details.withdraw->reserve_pub; + TALER_amount_hton (&wr.amount_with_fee, + &value); + TALER_amount_hton (&wr.withdraw_fee, + &pos->details.withdraw->withdraw_fee); + GNUNET_CRYPTO_rsa_public_key_hash (pos->details.withdraw->denom_pub.rsa_public_key, + &wr.h_denomination_pub); + wr.h_coin_envelope = pos->details.withdraw->h_coin_envelope; + + transaction = TALER_json_from_eddsa_sig (&wr.purpose, + &pos->details.withdraw->reserve_sig.eddsa_signature); + + json_array_append_new (json_history, + json_pack ("{s:s, s:o, s:o}", + "type", "WITHDRAW", + "signature", transaction, + "amount", TALER_json_from_amount (&value))); + break; + } + } + if (0 == ret) + { + /* did not encounter any withdraw operations, set to zero */ + TALER_amount_get_zero (deposit_total.currency, + &withdraw_total); + } + if (GNUNET_SYSERR == + TALER_amount_subtract (balance, + &deposit_total, + &withdraw_total)) + { + GNUNET_break (0); + json_decref (json_history); + return NULL; + } + + return json_history; +} + + +/** + * Send reserve status information to client. + * + * @param connection connection to the client + * @param rh reserve history to return + * @return MHD result code + */ +int +TMH_RESPONSE_reply_withdraw_status_success (struct MHD_Connection *connection, + const struct TALER_MINTDB_ReserveHistory *rh) +{ + json_t *json_balance; + json_t *json_history; + struct TALER_Amount balance; + + json_history = compile_reserve_history (rh, + &balance); + if (NULL == json_history) + return TMH_RESPONSE_reply_internal_error (connection, + "balance calculation failure"); + json_balance = TALER_json_from_amount (&balance); + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:o, s:o}", + "balance", json_balance, + "history", json_history); +} + + +/** + * Send reserve status information to client with the + * message that we have insufficient funds for the + * requested /withdraw/sign operation. + * + * @param connection connection to the client + * @param rh reserve history to return + * @return MHD result code + */ +int +TMH_RESPONSE_reply_withdraw_sign_insufficient_funds (struct MHD_Connection *connection, + const struct TALER_MINTDB_ReserveHistory *rh) +{ + json_t *json_balance; + json_t *json_history; + struct TALER_Amount balance; + + json_history = compile_reserve_history (rh, + &balance); + if (NULL == json_history) + return TMH_RESPONSE_reply_internal_error (connection, + "balance calculation failure"); + json_balance = TALER_json_from_amount (&balance); + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_PAYMENT_REQUIRED, + "{s:s, s:o, s:o}", + "error", "Insufficient funds", + "balance", json_balance, + "history", json_history); +} + + +/** + * Send blinded coin information to client. + * + * @param connection connection to the client + * @param collectable blinded coin to return + * @return MHD result code + */ +int +TMH_RESPONSE_reply_withdraw_sign_success (struct MHD_Connection *connection, + const struct TALER_MINTDB_CollectableBlindcoin *collectable) +{ + json_t *sig_json; + + sig_json = TALER_json_from_rsa_signature (collectable->sig.rsa_signature); + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:o}", + "ev_sig", sig_json); +} + + +/** + * Send a response for a failed "/refresh/melt" request. The + * transaction history of the given coin demonstrates that the + * @a residual value of the coin is below the @a requested + * contribution of the coin for the melt. Thus, the mint + * refuses the melt operation. + * + * @param connection the connection to send the response to + * @param coin_pub public key of the coin + * @param coin_value original value of the coin + * @param tl transaction history for the coin + * @param requested how much this coin was supposed to contribute + * @param residual remaining value of the coin (after subtracting @a tl) + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_refresh_melt_insufficient_funds (struct MHD_Connection *connection, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_Amount coin_value, + struct TALER_MINTDB_TransactionList *tl, + struct TALER_Amount requested, + struct TALER_Amount residual) +{ + json_t *history; + + history = compile_transaction_history (tl); + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:s, s:o, s:o, s:o, s:o, s:o}", + "error", "insufficient funds", + "coin-pub", TALER_json_from_data (coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP)), + "original-value", TALER_json_from_amount (&coin_value), + "residual-value", TALER_json_from_amount (&residual), + "requested-value", TALER_json_from_amount (&requested), + "history", history); +} + + +/** + * Send a response to a "/refresh/melt" request. + * + * @param connection the connection to send the response to + * @param session_hash hash of the refresh session + * @param noreveal_index which index will the client not have to reveal + * @return a MHD status code + */ +int +TMH_RESPONSE_reply_refresh_melt_success (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + uint16_t noreveal_index) +{ + struct TALER_RefreshMeltConfirmationPS body; + struct TALER_MintPublicKeyP pub; + struct TALER_MintSignatureP sig; + json_t *sig_json; + + body.purpose.size = htonl (sizeof (struct TALER_RefreshMeltConfirmationPS)); + body.purpose.purpose = htonl (TALER_SIGNATURE_MINT_CONFIRM_MELT); + body.session_hash = *session_hash; + body.noreveal_index = htons (noreveal_index); + TMH_KS_sign (&body.purpose, + &pub, + &sig); + sig_json = TALER_json_from_data (&sig, + sizeof (sig)); + GNUNET_assert (NULL != sig_json); + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:i, s:o, s:o}", + "noreveal_index", (int) noreveal_index, + "mint_sig", sig_json, + "mint_pub", TALER_json_from_data (&pub, + sizeof (pub))); +} + + +/** + * Send a response for "/refresh/reveal". + * + * @param connection the connection to send the response to + * @param num_newcoins number of new coins for which we reveal data + * @param sigs array of @a num_newcoins signatures revealed + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_refresh_reveal_success (struct MHD_Connection *connection, + unsigned int num_newcoins, + const struct TALER_DenominationSignature *sigs) +{ + int newcoin_index; + json_t *root; + json_t *list; + int ret; + + root = json_object (); + list = json_array (); + json_object_set_new (root, + "ev_sigs", + list); + for (newcoin_index = 0; newcoin_index < num_newcoins; newcoin_index++) + json_array_append_new (list, + TALER_json_from_rsa_signature (sigs[newcoin_index].rsa_signature)); + ret = TMH_RESPONSE_reply_json (connection, + root, + MHD_HTTP_OK); + json_decref (root); + return ret; +} + + +/** + * Send a response for a failed "/refresh/reveal", where the + * revealed value(s) do not match the original commitment. + * + * @param connection the connection to send the response to + * @param mc all information about the original commitment + * @param off offset in the array of kappa-commitments where + * the missmatch was detected + * @param j index of the coin for which the missmatch was + * detected + * @param missmatch_object name of the object that was + * bogus (i.e. "transfer key"). + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_refresh_reveal_missmatch (struct MHD_Connection *connection, + const struct TALER_MINTDB_MeltCommitment *mc, + unsigned int off, + unsigned int j, + const char *missmatch_object) +{ + json_t *info_old; + json_t *info_new; + json_t *info_commit; + json_t *info_links; + unsigned int i; + unsigned int k; + + info_old = json_array (); + for (i=0;i<mc->num_oldcoins;i++) + { + const struct TALER_MINTDB_RefreshMelt *rm; + json_t *rm_json; + + rm = &mc->melts[i]; + rm_json = json_object (); + json_object_set_new (rm_json, + "coin_sig", + TALER_json_from_data (&rm->coin_sig, + sizeof (struct TALER_CoinSpendSignatureP))); + json_object_set_new (rm_json, + "coin_pub", + TALER_json_from_data (&rm->coin.coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP))); + json_object_set_new (rm_json, + "melt_amount_with_fee", + TALER_json_from_amount (&rm->amount_with_fee)); + json_object_set_new (rm_json, + "melt_fee", + TALER_json_from_amount (&rm->melt_fee)); + json_array_append_new (info_old, + rm_json); + } + info_new = json_array (); + for (i=0;i<mc->num_newcoins;i++) + { + const struct TALER_DenominationPublicKey *pk; + + pk = &mc->denom_pubs[i]; + json_array_append_new (info_new, + TALER_json_from_rsa_public_key (pk->rsa_public_key)); + + } + info_commit = json_array (); + info_links = json_array (); + for (k=0;k<TALER_CNC_KAPPA;k++) + { + json_t *info_commit_k; + json_t *info_link_k; + + info_commit_k = json_array (); + for (i=0;i<mc->num_newcoins;i++) + { + const struct TALER_MINTDB_RefreshCommitCoin *cc; + json_t *cc_json; + + cc = &mc->commit_coins[k][i]; + cc_json = json_object (); + json_object_set_new (cc_json, + "coin_ev", + TALER_json_from_data (cc->coin_ev, + cc->coin_ev_size)); + json_object_set_new (cc_json, + "coin_priv_enc", + TALER_json_from_data (cc->refresh_link->coin_priv_enc, + sizeof (struct TALER_CoinSpendPrivateKeyP))); + json_object_set_new (cc_json, + "blinding_key_enc", + TALER_json_from_data (cc->refresh_link->blinding_key_enc, + cc->refresh_link->blinding_key_enc_size)); + + json_array_append_new (info_commit_k, + cc_json); + } + json_array_append_new (info_commit, + info_commit_k); + info_link_k = json_array (); + for (i=0;i<mc->num_newcoins;i++) + { + const struct TALER_MINTDB_RefreshCommitLinkP *cl; + json_t *cl_json; + + cl = &mc->commit_links[k][i]; + cl_json = json_object (); + json_object_set_new (cl_json, + "transfer_pub", + TALER_json_from_data (&cl->transfer_pub, + sizeof (struct TALER_TransferPublicKeyP))); + json_object_set_new (cl_json, + "shared_secret_enc", + TALER_json_from_data (&cl->shared_secret_enc, + sizeof (struct TALER_EncryptedLinkSecretP))); + json_array_append_new (info_link_k, + cl_json); + } + json_array_append_new (info_links, + info_link_k); + } + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_CONFLICT, + "{s:s, s:i, s:i, s:o, s:o, s:o, s:o, s:s}", + "error", "commitment violation", + "offset", (int) off, + "index", (int) j, + "oldcoin_infos", info_old, + "newcoin_infos", info_new, + "commit_infos", info_commit, + "link_infos", info_links, + "object", missmatch_object); +} + + +/** + * Send a response for "/refresh/link". + * + * @param connection the connection to send the response to + * @param num_sessions number of sessions the coin was used in + * @param sessions array of @a num_session entries with + * information for each session + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_refresh_link_success (struct MHD_Connection *connection, + unsigned int num_sessions, + const struct TMH_RESPONSE_LinkSessionInfo *sessions) +{ + json_t *root; + json_t *mlist; + int res; + unsigned int i; + + mlist = json_array (); + for (i=0;i<num_sessions;i++) + { + const struct TALER_MINTDB_LinkDataList *pos; + json_t *list = json_array (); + + for (pos = sessions[i].ldl; NULL != pos; pos = pos->next) + { + json_t *obj; + + obj = json_object (); + json_object_set_new (obj, + "link_enc", + TALER_json_from_data (pos->link_data_enc->coin_priv_enc, + sizeof (struct TALER_CoinSpendPrivateKeyP) + + pos->link_data_enc->blinding_key_enc_size)); + json_object_set_new (obj, + "denom_pub", + TALER_json_from_rsa_public_key (pos->denom_pub.rsa_public_key)); + json_object_set_new (obj, + "ev_sig", + TALER_json_from_rsa_signature (pos->ev_sig.rsa_signature)); + json_array_append_new (list, + obj); + } + root = json_object (); + json_object_set_new (root, + "new_coins", + list); + json_object_set_new (root, + "transfer_pub", + TALER_json_from_data (&sessions[i].transfer_pub, + sizeof (struct TALER_TransferPublicKeyP))); + json_object_set_new (root, + "secret_enc", + TALER_json_from_data (&sessions[i].shared_secret_enc, + sizeof (struct TALER_EncryptedLinkSecretP))); + json_array_append_new (mlist, + root); + } + res = TMH_RESPONSE_reply_json (connection, + mlist, + MHD_HTTP_OK); + json_decref (mlist); + return res; +} + + +/* end of taler-mint-httpd_responses.c */ diff --git a/src/backend/taler-mint-httpd_responses.h b/src/backend/taler-mint-httpd_responses.h new file mode 100644 index 00000000..7afd0188 --- /dev/null +++ b/src/backend/taler-mint-httpd_responses.h @@ -0,0 +1,390 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd_responses.h + * @brief API for generating the various replies of the mint; these + * functions are called TMH_RESPONSE_reply_ and they generate + * and queue MHD response objects for a given connection. + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_RESPONSES_H +#define TALER_MINT_HTTPD_RESPONSES_H +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <pthread.h> +#include "taler-mint-httpd.h" +#include "taler-mint-httpd_db.h" + + +/** + * Send JSON object as response. + * + * @param connection the MHD connection + * @param json the json object + * @param response_code the http response code + * @return MHD result code + */ +int +TMH_RESPONSE_reply_json (struct MHD_Connection *connection, + const json_t *json, + unsigned int response_code); + + +/** + * Function to call to handle the request by building a JSON + * reply from a format string and varargs. + * + * @param connection the MHD connection to handle + * @param response_code HTTP response code to use + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TMH_RESPONSE_reply_json_pack (struct MHD_Connection *connection, + unsigned int response_code, + const char *fmt, + ...); + + +/** + * Send a response indicating an invalid signature. + * + * @param connection the MHD connection to use + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_signature_invalid (struct MHD_Connection *connection, + const char *param_name); + + +/** + * Send a response indicating an invalid argument. + * + * @param connection the MHD connection to use + * @param param_name the parameter that is invalid + * @return MHD result code + */ +int +TMH_RESPONSE_reply_arg_invalid (struct MHD_Connection *connection, + const char *param_name); + + +/** + * Send a response indicating an argument refering to a + * resource unknown to the mint (i.e. unknown reserve or + * denomination key). + * + * @param connection the MHD connection to use + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_arg_unknown (struct MHD_Connection *connection, + const char *param_name); + + +/** + * Send a response indicating a missing argument. + * + * @param connection the MHD connection to use + * @param param_name the parameter that is missing + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_arg_missing (struct MHD_Connection *connection, + const char *param_name); + + +/** + * Send a response indicating permission denied. + * + * @param connection the MHD connection to use + * @param hint hint about why access was denied + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_permission_denied (struct MHD_Connection *connection, + const char *hint); + + +/** + * Send a response indicating an internal error. + * + * @param connection the MHD connection to use + * @param hint hint about the internal error's nature + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, + const char *hint); + + +/** + * Send a response indicating an external error. + * + * @param connection the MHD connection to use + * @param hint hint about the error's nature + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, + const char *hint); + + +/** + * Send a response indicating an error committing a + * transaction (concurrent interference). + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_commit_error (struct MHD_Connection *connection); + + +/** + * Send a response indicating a failure to talk to the Mint's + * database. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_internal_db_error (struct MHD_Connection *connection); + + +/** + * Send a response indicating that the request was too big. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_request_too_large (struct MHD_Connection *connection); + + +/** + * Send a response indicating that the JSON was malformed. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection); + + +/** + * Send confirmation of deposit success to client. This function + * will create a signed message affirming the given information + * and return it to the client. By this, the mint affirms that + * the coin had sufficient (residual) value for the specified + * transaction and that it will execute the requested deposit + * operation with the given wiring details. + * + * @param connection connection to the client + * @param coin_pub public key of the coin + * @param h_wire hash of wire details + * @param h_contract hash of contract details + * @param transaction_id transaction ID + * @param timestamp client's timestamp + * @param refund_deadline until when this deposit be refunded + * @param merchant merchant public key + * @param amount_without_fee fraction of coin value to deposit (without fee) + * @return MHD result code + */ +int +TMH_RESPONSE_reply_deposit_success (struct MHD_Connection *connection, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + uint64_t transaction_id, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund_deadline, + const struct TALER_MerchantPublicKeyP *merchant, + const struct TALER_Amount *amount_without_fee); + + +/** + * Send proof that a /deposit request is invalid to client. This + * function will create a message with all of the operations affecting + * the coin that demonstrate that the coin has insufficient value. + * + * @param connection connection to the client + * @param tl transaction list to use to build reply + * @return MHD result code + */ +int +TMH_RESPONSE_reply_deposit_insufficient_funds (struct MHD_Connection *connection, + const struct TALER_MINTDB_TransactionList *tl); + + +/** + * Send reserve status information to client. + * + * @param connection connection to the client + * @param rh reserve history to return + * @return MHD result code + */ +int +TMH_RESPONSE_reply_withdraw_status_success (struct MHD_Connection *connection, + const struct TALER_MINTDB_ReserveHistory *rh); + + +/** + * Send reserve status information to client with the + * message that we have insufficient funds for the + * requested /withdraw/sign operation. + * + * @param connection connection to the client + * @param rh reserve history to return + * @return MHD result code + */ +int +TMH_RESPONSE_reply_withdraw_sign_insufficient_funds (struct MHD_Connection *connection, + const struct TALER_MINTDB_ReserveHistory *rh); + + +/** + * Send blinded coin information to client. + * + * @param connection connection to the client + * @param collectable blinded coin to return + * @return MHD result code + */ +int +TMH_RESPONSE_reply_withdraw_sign_success (struct MHD_Connection *connection, + const struct TALER_MINTDB_CollectableBlindcoin *collectable); + + +/** + * Send a confirmation response to a "/refresh/melt" request. + * + * @param connection the connection to send the response to + * @param session_hash hash of the refresh session + * @param noreveal_index which index will the client not have to reveal + * @return a MHD status code + */ +int +TMH_RESPONSE_reply_refresh_melt_success (struct MHD_Connection *connection, + const struct GNUNET_HashCode *session_hash, + uint16_t noreveal_index); + + +/** + * Send a response for a failed "/refresh/melt" request. The + * transaction history of the given coin demonstrates that the + * @a residual value of the coin is below the @a requested + * contribution of the coin for the melt. Thus, the mint + * refuses the melt operation. + * + * @param connection the connection to send the response to + * @param coin_pub public key of the coin + * @param coin_value original value of the coin + * @param tl transaction history for the coin + * @param requested how much this coin was supposed to contribute + * @param residual remaining value of the coin (after subtracting @a tl) + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_refresh_melt_insufficient_funds (struct MHD_Connection *connection, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_Amount coin_value, + struct TALER_MINTDB_TransactionList *tl, + struct TALER_Amount requested, + struct TALER_Amount residual); + + +/** + * Send a response for "/refresh/reveal". + * + * @param connection the connection to send the response to + * @param num_newcoins number of new coins for which we reveal data + * @param sigs array of @a num_newcoins signatures revealed + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_refresh_reveal_success (struct MHD_Connection *connection, + unsigned int num_newcoins, + const struct TALER_DenominationSignature *sigs); + + +/** + * Send a response for a failed "/refresh/reveal", where the + * revealed value(s) do not match the original commitment. + * + * @param connection the connection to send the response to + * @param mc all information about the original commitment + * @param off offset in the array of kappa-commitments where + * the missmatch was detected + * @param j index of the coin for which the missmatch was + * detected + * @param missmatch_object name of the object that was + * bogus (i.e. "transfer key"). + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_refresh_reveal_missmatch (struct MHD_Connection *connection, + const struct TALER_MINTDB_MeltCommitment *mc, + unsigned int off, + unsigned int j, + const char *missmatch_object); + + +/** + * Information for each session a coin was melted into. + */ +struct TMH_RESPONSE_LinkSessionInfo +{ + /** + * Transfer public key of the coin. + */ + struct TALER_TransferPublicKeyP transfer_pub; + + /** + * Encrypted shared secret for decrypting the transfer secrets. + */ + struct TALER_EncryptedLinkSecretP shared_secret_enc; + + /** + * Linked data of coins being created in the session. + */ + struct TALER_MINTDB_LinkDataList *ldl; + +}; + + +/** + * Send a response for "/refresh/link". + * + * @param connection the connection to send the response to + * @param num_sessions number of sessions the coin was used in + * @param sessions array of @a num_session entries with + * information for each session + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_refresh_link_success (struct MHD_Connection *connection, + unsigned int num_sessions, + const struct TMH_RESPONSE_LinkSessionInfo *sessions); + + +#endif diff --git a/src/backend/taler-mint-httpd_withdraw.c b/src/backend/taler-mint-httpd_withdraw.c new file mode 100644 index 00000000..4f558164 --- /dev/null +++ b/src/backend/taler-mint-httpd_withdraw.c @@ -0,0 +1,180 @@ +/* + This file is part of TALER + Copyright (C) 2014,2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_withdraw.c + * @brief Handle /withdraw/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include "taler-mint-httpd_withdraw.h" +#include "taler-mint-httpd_parsing.h" +#include "taler-mint-httpd_responses.h" +#include "taler-mint-httpd_keystate.h" + + +/** + * Handle a "/withdraw/status" request. Parses the + * given "reserve_pub" argument (which should contain the + * EdDSA public key of a reserve) and then respond with the + * status of the reserve. + * + * @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 +TMH_WITHDRAW_handler_withdraw_status (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct TALER_ReservePublicKeyP reserve_pub; + int res; + + res = TMH_PARSE_mhd_request_arg_data (connection, + "reserve_pub", + &reserve_pub, + sizeof (struct TALER_ReservePublicKeyP)); + if (GNUNET_SYSERR == res) + return MHD_NO; /* internal error */ + if (GNUNET_NO == res) + return MHD_YES; /* parse error */ + return TMH_DB_execute_withdraw_status (connection, + &reserve_pub); +} + + +/** + * Handle a "/withdraw/sign" request. Parses the "reserve_pub" + * EdDSA key of the reserve and the requested "denom_pub" which + * specifies the key/value of the coin to be withdrawn, and checks + * that the signature "reserve_sig" makes this a valid withdrawl + * request from the specified reserve. If so, the envelope + * with the blinded coin "coin_ev" is passed down to execute the + * withdrawl operation. + * + * @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 +TMH_WITHDRAW_handler_withdraw_sign (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + json_t *root; + struct TALER_WithdrawRequestPS wsrd; + int res; + struct TALER_DenominationPublicKey denomination_pub; + char *blinded_msg; + size_t blinded_msg_len; + struct TALER_Amount amount; + struct TALER_Amount amount_with_fee; + struct TALER_Amount fee_withdraw; + struct TALER_ReserveSignatureP signature; + struct TALER_MINTDB_DenominationKeyIssueInformation *dki; + struct TMH_KS_StateHandle *ks; + + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_variable ("coin_ev", (void **) &blinded_msg, &blinded_msg_len), + TMH_PARSE_member_fixed ("reserve_pub", &wsrd.reserve_pub), + TMH_PARSE_member_fixed ("reserve_sig", &signature), + TMH_PARSE_member_denomination_public_key ("denom_pub", &denomination_pub), + TMH_PARSE_MEMBER_END + }; + + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + return MHD_NO; + if ( (GNUNET_NO == res) || (NULL == root) ) + return MHD_YES; + res = TMH_PARSE_json_data (connection, + root, + spec); + json_decref (root); + if (GNUNET_OK != res) + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + ks = TMH_KS_acquire (); + dki = TMH_KS_denomination_key_lookup (ks, + &denomination_pub, + TMH_KS_DKU_WITHDRAW); + if (NULL == dki) + { + TMH_PARSE_release_data (spec); + return TMH_RESPONSE_reply_arg_unknown (connection, + "denom_pub"); + } + TALER_amount_ntoh (&amount, + &dki->issue.properties.value); + TALER_amount_ntoh (&fee_withdraw, + &dki->issue.properties.fee_withdraw); + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&amount_with_fee, + &amount, + &fee_withdraw)); + TALER_amount_hton (&wsrd.amount_with_fee, + &amount_with_fee); + TALER_amount_hton (&wsrd.withdraw_fee, + &fee_withdraw); + TMH_KS_release (ks); + /* verify signature! */ + wsrd.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS)); + wsrd.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); + + GNUNET_CRYPTO_rsa_public_key_hash (denomination_pub.rsa_public_key, + &wsrd.h_denomination_pub); + GNUNET_CRYPTO_hash (blinded_msg, + blinded_msg_len, + &wsrd.h_coin_envelope); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, + &wsrd.purpose, + &signature.eddsa_signature, + &wsrd.reserve_pub.eddsa_pub)) + { + TALER_LOG_WARNING ("Client supplied invalid signature for /withdraw/sign request\n"); + TMH_PARSE_release_data (spec); + return TMH_RESPONSE_reply_signature_invalid (connection, + "reserve_sig"); + } + res = TMH_DB_execute_withdraw_sign (connection, + &wsrd.reserve_pub, + &denomination_pub, + blinded_msg, + blinded_msg_len, + &signature); + TMH_PARSE_release_data (spec); + return res; +} + +/* end of taler-mint-httpd_withdraw.c */ diff --git a/src/backend/taler-mint-httpd_withdraw.h b/src/backend/taler-mint-httpd_withdraw.h new file mode 100644 index 00000000..668178b1 --- /dev/null +++ b/src/backend/taler-mint-httpd_withdraw.h @@ -0,0 +1,73 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd_withdraw.h + * @brief Handle /withdraw/ requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_WITHDRAW_H +#define TALER_MINT_HTTPD_WITHDRAW_H + +#include <microhttpd.h> +#include "taler-mint-httpd.h" + +/** + * Handle a "/withdraw/status" request. Parses the + * given "reserve_pub" argument (which should contain the + * EdDSA public key of a reserve) and then respond with the + * status of the reserve. + * + * @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 +TMH_WITHDRAW_handler_withdraw_status (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Handle a "/withdraw/sign" request. Parses the "reserve_pub" + * EdDSA key of the reserve and the requested "denom_pub" which + * specifies the key/value of the coin to be withdrawn, and checks + * that the signature "reserve_sig" makes this a valid withdrawl + * request from the specified reserve. If so, the envelope + * with the blinded coin "coin_ev" is passed down to execute the + * withdrawl operation. + * + * @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 +TMH_WITHDRAW_handler_withdraw_sign (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif diff --git a/src/backend/taler_amount_lib.h b/src/backend/taler_amount_lib.h new file mode 100644 index 00000000..8661ed91 --- /dev/null +++ b/src/backend/taler_amount_lib.h @@ -0,0 +1,273 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/taler_amount_lib.h + * @brief amount-representation utility functions + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#ifndef TALER_AMOUNT_LIB_H +#define TALER_AMOUNT_LIB_H + +#ifdef __cplusplus +extern "C" +{ +#if 0 /* keep Emacsens' auto-indent happy */ +} +#endif +#endif + +#include <gnunet/platform.h> + + +/** + * @brief Number of characters (plus 1 for 0-termination) we use to + * represent currency names (i.e. EUR, USD, etc.). We use 8+4 for + * alignment in the `struct TALER_Amount`. The amount is typically an + * ISO 4217 currency code when an alpha-numeric 3-digit code is used. + * For regional currencies, the first character should be a "*" followed + * by a region-specific name (i.e. "*BRETAGNEFR"). + */ +#define TALER_CURRENCY_LEN 12 + +/** + * Taler currency length as a string. + */ +#define TALER_CURRENCY_LEN_STR "12" + +/** + * @brief The "fraction" value in a `struct TALER_Amount` represents which + * fraction of the "main" value? + * + * Note that we need sub-cent precision here as transaction fees might + * be that low, and as we want to support microdonations. + */ +#define TALER_AMOUNT_FRAC_BASE 1000000 + +/** + * @brief How many digits behind the comma are required to represent the + * fractional value in human readable decimal format? Must match + * lg(#TALER_AMOUNT_FRAC_BASE). + */ +#define TALER_AMOUNT_FRAC_LEN 6 + + +GNUNET_NETWORK_STRUCT_BEGIN + + +/** + * @brief Amount, encoded for network transmission. + */ +struct TALER_AmountNBO +{ + /** + * Value in the main currency, in NBO. + */ + uint64_t value GNUNET_PACKED; + + /** + * Additinal fractional value, in NBO. + */ + uint32_t fraction GNUNET_PACKED; + + /** + * Type of the currency being represented. + */ + char currency[TALER_CURRENCY_LEN]; +}; + +GNUNET_NETWORK_STRUCT_END + + +/** + * @brief Representation of monetary value in a given currency. + */ +struct TALER_Amount +{ + /** + * Value (numerator of fraction) + */ + uint64_t value; + + /** + * Fraction (denominator of fraction) + */ + uint32_t fraction; + + /** + * Currency string, left adjusted and padded with zeros. All zeros + * for "invalid" values. + */ + char currency[TALER_CURRENCY_LEN]; +}; + + +/** + * 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); + + +/** + * Get the value of "zero" in a particular currency. + * + * @param cur currency description + * @param denom denomination to write the result to + * @return #GNUNET_OK if @a cur is a valid currency specification, + * #GNUNET_SYSERR if it is invalid. + */ +int +TALER_amount_get_zero (const char *cur, + struct TALER_Amount *denom); + + +/** + * Convert amount from host to network representation. + * + * @param res where to store amount in network representation + * @param d amount in host representation + */ +void +TALER_amount_hton (struct TALER_AmountNBO *res, + const struct TALER_Amount *d); + + +/** + * Convert amount from network to host representation. + * + * @param res where to store amount in host representation + * @param dn amount in network representation + */ +void +TALER_amount_ntoh (struct TALER_Amount *res, + const struct TALER_AmountNBO *dn); + + +/** + * Compare the value/fraction of two amounts. Does not compare the currency. + * Comparing amounts of different currencies will cause the program to abort(). + * If unsure, check with #TALER_amount_cmp_currency() first to be sure that + * the currencies of the two amounts are identical. + * + * @param a1 first amount + * @param a2 second amount + * @return result of the comparison + * -1 if `a1 < a2` + * 1 if `a1 > a2` + * 0 if `a1 == a2`. + */ +int +TALER_amount_cmp (const struct TALER_Amount *a1, + const struct TALER_Amount *a2); + + +/** + * Test if @a a1 and @a a2 are the same currency. + * + * @param a1 amount to test + * @param a2 amount to test + * @return #GNUNET_YES if @a a1 and @a a2 are the same currency + * #GNUNET_NO if the currencies are different + * #GNUNET_SYSERR if either amount is invalid + */ +int +TALER_amount_cmp_currency (const struct TALER_Amount *a1, + const struct TALER_Amount *a2); + + +/** + * Test if @a a1 and @a a2 are the same currency, NBO variant. + * + * @param a1 amount to test + * @param a2 amount to test + * @return #GNUNET_YES if @a a1 and @a a2 are the same currency + * #GNUNET_NO if the currencies are different + * #GNUNET_SYSERR if either amount is invalid + */ +int +TALER_amount_cmp_currency_nbo (const struct TALER_AmountNBO *a1, + const struct TALER_AmountNBO *a2); + + +/** + * Perform saturating subtraction of amounts. + * + * @param diff where to store (@a a1 - @a a2), or invalid if @a a2 > @a a1 + * @param a1 amount to subtract from + * @param a2 amount to subtract + * @return #GNUNET_OK if the subtraction worked, + * #GNUNET_NO if @a a1 = @a a2 + * #GNUNET_SYSERR if @a a2 > @a a1 or currencies are incompatible; + * @a diff is set to invalid + */ +int +TALER_amount_subtract (struct TALER_Amount *diff, + const struct TALER_Amount *a1, + const struct TALER_Amount *a2); + + +/** + * Perform addition of amounts. + * + * @param sum where to store @a a1 + @a a2, set to "invalid" on overflow + * @param a1 first amount to add + * @param a2 second amount to add + * @return #GNUNET_OK if the addition worked, + * #GNUNET_SYSERR on overflow + */ +int +TALER_amount_add (struct TALER_Amount *sum, + const struct TALER_Amount *a1, + const struct TALER_Amount *a2); + + +/** + * Normalize the given amount. + * + * @param amount amount to normalize + * @return #GNUNET_OK if normalization worked + * #GNUNET_NO if value was already normalized + * #GNUNET_SYSERR if value was invalid or could not be normalized + */ +int +TALER_amount_normalize (struct TALER_Amount *amount); + + +/** + * Convert amount to string. + * + * @param amount amount to convert to string + * @return freshly allocated string representation, + * NULL if the @a amount was invalid + */ +char * +TALER_amount_to_string (const struct TALER_Amount *amount); + +#if 0 /* keep Emacsens' auto-indent happy */ +{ +#endif +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/src/backend/taler_crypto_lib.h b/src/backend/taler_crypto_lib.h new file mode 100644 index 00000000..4126894a --- /dev/null +++ b/src/backend/taler_crypto_lib.h @@ -0,0 +1,569 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/taler_crypto_lib.h + * @brief taler-specific crypto functions + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff <christian@grothoff.org> + */ +#ifndef TALER_CRYPTO_LIB_H +#define TALER_CRYPTO_LIB_H + +#if HAVE_GNUNET_GNUNET_UTIL_LIB_H +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#elif HAVE_GNUNET_GNUNET_UTIL_TALER_WALLET_LIB_H +#include <gnunet/gnunet_util_taler_wallet_lib.h> +#include "taler_util_wallet.h" +#endif + +#include <gcrypt.h> + + +/* ****************** Coin crypto primitives ************* */ + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Type of public keys for Taler reserves. + */ +struct TALER_ReservePublicKeyP +{ + /** + * Taler uses EdDSA for reserves. + */ + struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub; +}; + + +/** + * @brief Type of private keys for Taler reserves. + */ +struct TALER_ReservePrivateKeyP +{ + /** + * Taler uses EdDSA for reserves. + */ + struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv; +}; + + +/** + * @brief Type of signatures used with Taler reserves. + */ +struct TALER_ReserveSignatureP +{ + /** + * Taler uses EdDSA for reserves. + */ + struct GNUNET_CRYPTO_EddsaSignature eddsa_signature; +}; + + +/** + * @brief Type of public keys to for merchant authorizations. + * Merchants can issue refunds using the corresponding + * private key. + */ +struct TALER_MerchantPublicKeyP +{ + /** + * Taler uses EdDSA for merchants. + */ + struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub; +}; + + +/** + * @brief Type of private keys for merchant authorizations. + * Merchants can issue refunds using the corresponding + * private key. + */ +struct TALER_MerchantPrivateKeyP +{ + /** + * Taler uses EdDSA for merchants. + */ + struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv; +}; + + +/** + * @brief Type of transfer public keys used during refresh + * operations. + */ +struct TALER_TransferPublicKeyP +{ + /** + * Taler uses ECDHE for transfer keys. + */ + struct GNUNET_CRYPTO_EcdhePublicKey ecdhe_pub; +}; + + +/** + * @brief Type of transfer public keys used during refresh + * operations. + */ +struct TALER_TransferPrivateKeyP +{ + /** + * Taler uses ECDHE for melting session keys. + */ + struct GNUNET_CRYPTO_EcdhePrivateKey ecdhe_priv; +}; + + +/** + * @brief Type of online public keys used by the mint to sign + * messages. + */ +struct TALER_MintPublicKeyP +{ + /** + * Taler uses EdDSA for online mint message signing. + */ + struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub; +}; + + +/** + * @brief Type of online public keys used by the mint to + * sign messages. + */ +struct TALER_MintPrivateKeyP +{ + /** + * Taler uses EdDSA for online signatures sessions. + */ + struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv; +}; + + +/** + * @brief Type of signatures used by the mint to sign messages online. + */ +struct TALER_MintSignatureP +{ + /** + * Taler uses EdDSA for online signatures sessions. + */ + struct GNUNET_CRYPTO_EddsaSignature eddsa_signature; +}; + + +/** + * @brief Type of the offline master public key used by the mint. + */ +struct TALER_MasterPublicKeyP +{ + /** + * Taler uses EdDSA for the long-term offline master key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub; +}; + + +/** + * @brief Type of the public key used by the auditor. + */ +struct TALER_AuditorPublicKeyP +{ + /** + * Taler uses EdDSA for the auditor's signing key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub; +}; + + +/** + * @brief Type of the offline master public keys used by the mint. + */ +struct TALER_MasterPrivateKeyP +{ + /** + * Taler uses EdDSA for the long-term offline master key. + */ + struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv; +}; + + +/** + * @brief Type of signatures by the offline master public key used by the mint. + */ +struct TALER_MasterSignatureP +{ + /** + * Taler uses EdDSA for the long-term offline master key. + */ + struct GNUNET_CRYPTO_EddsaSignature eddsa_signature; +}; + + + +/** + * @brief Type of public keys for Taler coins. The same key material is used + * for EdDSA and ECDHE operations. + */ +struct TALER_CoinSpendPublicKeyP +{ + /** + * Taler uses EdDSA for coins when signing deposit requests. + */ + struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub; + +}; + + +/** + * @brief Type of private keys for Taler coins. The same key material is used + * for EdDSA and ECDHE operations. + */ +struct TALER_CoinSpendPrivateKeyP +{ + /** + * Taler uses EdDSA for coins when signing deposit requests. + */ + struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv; +}; + + +/** + * @brief Type of signatures made with Taler coins. + */ +struct TALER_CoinSpendSignatureP +{ + /** + * Taler uses EdDSA for coins. + */ + struct GNUNET_CRYPTO_EddsaSignature eddsa_signature; +}; + + +GNUNET_NETWORK_STRUCT_END + +/** + * @brief Type of blinding keys for Taler. + */ +struct TALER_DenominationBlindingKey +{ + /** + * Taler uses RSA for blinding. + */ + struct GNUNET_CRYPTO_rsa_BlindingKey *rsa_blinding_key; +}; + + +/** + * @brief Type of (unblinded) coin signatures for Taler. + */ +struct TALER_DenominationSignature +{ + /** + * Taler uses RSA for blinding. + */ + struct GNUNET_CRYPTO_rsa_Signature *rsa_signature; +}; + + +/** + * @brief Type of public signing keys for verifying blindly signed coins. + */ +struct TALER_DenominationPublicKey +{ + /** + * Taler uses RSA for signing coins. + */ + struct GNUNET_CRYPTO_rsa_PublicKey *rsa_public_key; +}; + + +/** + * @brief Type of private signing keys for blind signing of coins. + */ +struct TALER_DenominationPrivateKey +{ + /** + * Taler uses RSA for signing coins. + */ + struct GNUNET_CRYPTO_rsa_PrivateKey *rsa_private_key; +}; + + +/** + * @brief Public information about a coin (including the public key + * of the coin, the denomination key and the signature with + * the denomination key). + */ +struct TALER_CoinPublicInfo +{ + /** + * The coin's public key. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Public key representing the denomination of the coin + * that is being deposited. + */ + struct TALER_DenominationPublicKey denom_pub; + + /** + * (Unblinded) signature over @e coin_pub with @e denom_pub, + * which demonstrates that the coin is valid. + */ + struct TALER_DenominationSignature denom_sig; +}; + + +/** + * Check if a coin is valid; that is, whether the denomination key exists, + * is not expired, and the signature is correct. + * + * @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_test_coin_valid (const struct TALER_CoinPublicInfo *coin_public_info); + + +/* ****************** Refresh crypto primitives ************* */ + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Secret used to decrypt the key to decrypt link secrets. + */ +struct TALER_TransferSecretP +{ + /** + * Secret used to encrypt/decrypt the `struct TALER_LinkSecretP`. + * Must be (currently) a hash as this is what + * #GNUNET_CRYPTO_ecc_ecdh() returns to us. + */ + struct GNUNET_HashCode key; +}; + + +/** + * @brief Secret used to decrypt refresh links. + */ +struct TALER_LinkSecretP +{ + /** + * Secret used to decrypt the refresh link data. + */ + char key[sizeof (struct GNUNET_HashCode)]; +}; + + +/** + * @brief Encrypted secret used to decrypt refresh links. + */ +struct TALER_EncryptedLinkSecretP +{ + /** + * Encrypted secret, must be the given size! + */ + char enc[sizeof (struct TALER_LinkSecretP)]; +}; + + +/** + * @brief Representation of an refresh link in cleartext. + */ +struct TALER_RefreshLinkDecrypted +{ + + /** + * Private key of the coin. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + + /** + * Blinding key. + */ + struct TALER_DenominationBlindingKey blinding_key; + +}; + + +GNUNET_NETWORK_STRUCT_END + + +/** + * @brief Representation of an encrypted refresh link. + */ +struct TALER_RefreshLinkEncrypted +{ + + /** + * Encrypted blinding key with @e blinding_key_enc_size bytes, + * must be allocated at the end of this struct. + */ + const char *blinding_key_enc; + + /** + * Number of bytes in @e blinding_key_enc. + */ + size_t blinding_key_enc_size; + + /** + * Encrypted private key of the coin. + */ + char coin_priv_enc[sizeof (struct TALER_CoinSpendPrivateKeyP)]; + +}; + + +/** + * Decrypt the shared @a secret from the information in the + * encrypted link secret @e secret_enc using the transfer + * private key and the coin's public key. + * + * @param secret_enc encrypted link secret + * @param trans_priv transfer private key + * @param coin_pub coin public key + * @param[out] secret set to the shared secret + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +int +TALER_link_decrypt_secret (const struct TALER_EncryptedLinkSecretP *secret_enc, + const struct TALER_TransferPrivateKeyP *trans_priv, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_LinkSecretP *secret); + + +/** + * Decrypt the shared @a secret from the information in the + * encrypted link secret @e secret_enc using the transfer + * public key and the coin's private key. + * + * @param secret_enc encrypted link secret + * @param trans_pub transfer public key + * @param coin_priv coin private key + * @param[out] secret set to the shared secret + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +int +TALER_link_decrypt_secret2 (const struct TALER_EncryptedLinkSecretP *secret_enc, + const struct TALER_TransferPublicKeyP *trans_pub, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + struct TALER_LinkSecretP *secret); + + +/** + * Encrypt the shared @a secret to generate the encrypted link secret. + * Also creates the transfer key. + * + * @param secret link secret to encrypt + * @param coin_pub coin public key + * @param[out] trans_priv set to transfer private key + * @param[out] trans_pub set to transfer public key + * @param[out] secret_enc set to the encryptd @a secret + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +int +TALER_link_encrypt_secret (const struct TALER_LinkSecretP *secret, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_TransferPrivateKeyP *trans_priv, + struct TALER_TransferPublicKeyP *trans_pub, + struct TALER_EncryptedLinkSecretP *secret_enc); + + +/** + * Use the @a trans_sec (from ECDHE) to decrypt the @a secret_enc + * to obtain the @a secret to decrypt the linkage data. + * + * @param secret_enc encrypted secret + * @param trans_sec transfer secret + * @param secret shared secret for refresh link decryption + * @return #GNUNET_OK on success + */ +int +TALER_transfer_decrypt (const struct TALER_EncryptedLinkSecretP *secret_enc, + const struct TALER_TransferSecretP *trans_sec, + struct TALER_LinkSecretP *secret); + + +/** + * Use the @a trans_sec (from ECDHE) to encrypt the @a secret + * to obtain the @a secret_enc. + * + * @param secret shared secret for refresh link decryption + * @param trans_sec transfer secret + * @param[out] secret_enc encrypted secret + * @return #GNUNET_OK on success + */ +int +TALER_transfer_encrypt (const struct TALER_LinkSecretP *secret, + const struct TALER_TransferSecretP *trans_sec, + struct TALER_EncryptedLinkSecretP *secret_enc); + + +/** + * Decrypt refresh link information. + * + * @param input encrypted refresh link data + * @param secret shared secret to use for decryption + * @return NULL on error + */ +struct TALER_RefreshLinkDecrypted * +TALER_refresh_decrypt (const struct TALER_RefreshLinkEncrypted *input, + const struct TALER_LinkSecretP *secret); + + +/** + * Encrypt refresh link information. + * + * @param input plaintext refresh link data + * @param secret shared secret to use for encryption + * @return NULL on error (should never happen) + */ +struct TALER_RefreshLinkEncrypted * +TALER_refresh_encrypt (const struct TALER_RefreshLinkDecrypted *input, + const struct TALER_LinkSecretP *secret); + + +/** + * Decode encrypted refresh link information from buffer. + * + * @param buf buffer with refresh link data + * @param buf_len number of bytes in @a buf + * @return NULL on error (@a buf_len too small) + */ +struct TALER_RefreshLinkEncrypted * +TALER_refresh_link_encrypted_decode (const char *buf, + size_t buf_len); + + +/** + * Encode encrypted refresh link information to buffer. + * + * @param rle refresh link to encode + * @param[out] buf_len set number of bytes returned + * @return NULL on error, otherwise buffer with encoded @a rle + */ +char * +TALER_refresh_link_encrypted_encode (const struct TALER_RefreshLinkEncrypted *rle, + size_t *buf_len); + + + +#endif diff --git a/src/backend/taler_json_lib.h b/src/backend/taler_json_lib.h new file mode 100644 index 00000000..5a13b9bc --- /dev/null +++ b/src/backend/taler_json_lib.h @@ -0,0 +1,181 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/taler_json_lib.h + * @brief helper functions for JSON processing using libjansson + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#ifndef TALER_json_LIB_H_ +#define TALER_json_LIB_H_ + +#include <jansson.h> + +/** + * Print JSON parsing related error information + */ +#define TALER_json_warn(error) \ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \ + "JSON parsing failed at %s:%u: %s (%s)\n", \ + __FILE__, __LINE__, error.text, error.source) + + +/** + * 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 (const struct TALER_Amount *amount); + + +/** + * Convert absolute timestamp to a json string. + * + * @param stamp 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 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 + */ +json_t * +TALER_json_from_eddsa_sig (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose, + const struct GNUNET_CRYPTO_EddsaSignature *signature); + + +/** + * Convert RSA public key to JSON. + * + * @param pk public key to convert + * @return corresponding JSON encoding + */ +json_t * +TALER_json_from_rsa_public_key (struct GNUNET_CRYPTO_rsa_PublicKey *pk); + + +/** + * Convert RSA signature to JSON. + * + * @param sig signature to convert + * @return corresponding JSON encoding + */ +json_t * +TALER_json_from_rsa_signature (struct GNUNET_CRYPTO_rsa_Signature *sig); + + +/** + * 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[out] 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[out] 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 *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 @a out + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +int +TALER_json_to_data (json_t *json, + void *out, + size_t out_size); + + +/** + * Convert JSON to RSA public key. + * + * @param json JSON encoding to convert + * @return corresponding public key + */ +struct GNUNET_CRYPTO_rsa_PublicKey * +TALER_json_to_rsa_public_key (json_t *json); + + +/** + * Convert JSON to RSA signature. + * + * @param json JSON encoding to convert + * @return corresponding signature + */ +struct GNUNET_CRYPTO_rsa_Signature * +TALER_json_to_rsa_signature (json_t *json); + + +/** + * Hash a JSON for binary signing. + * + * @param[in] json some JSON value to hash + * @param[out] hc resulting hash code + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +int +TALER_hash_json (json_t *json, + struct GNUNET_HashCode *hc); + + +/** + * Check if the given wire format JSON object is correctly formatted + * + * @param type the type of the wire format + * @param wire the JSON wire format object + * @return #GNUNET_YES if correctly formatted; #GNUNET_NO if not + */ +int +TALER_json_validate_wireformat (const char *type, + const json_t *wire); + + +#endif /* TALER_json_LIB_H_ */ + +/* End of taler_json_lib.h */ diff --git a/src/backend/taler_mintdb_lib.h b/src/backend/taler_mintdb_lib.h new file mode 100644 index 00000000..24f67761 --- /dev/null +++ b/src/backend/taler_mintdb_lib.h @@ -0,0 +1,224 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/taler_mintdb_lib.h + * @brief IO operations for the mint's private keys + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINTDB_LIB_H +#define TALER_MINTDB_LIB_H + +#include "taler_signatures.h" + +/** + * Subdirectroy under the mint's base directory which contains + * the mint's signing keys. + */ +#define TALER_MINTDB_DIR_SIGNING_KEYS "signkeys" + +/** + * Subdirectory under the mint's base directory which contains + * the mint's denomination keys. + */ +#define TALER_MINTDB_DIR_DENOMINATION_KEYS "denomkeys" + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief On disk format used for a mint signing key. Signing keys are used + * by the mint to affirm its messages, but not to create coins. + * Includes the private key followed by the public information about + * the signing key. + */ +struct TALER_MINTDB_PrivateSigningKeyInformationP +{ + /** + * Private key part of the mint's signing key. + */ + struct TALER_MintPrivateKeyP signkey_priv; + + /** + * Public information about a mint signing key. + */ + struct TALER_MintSigningKeyValidityPS issue; +}; + + +/** + * Information about a denomination key. + */ +struct TALER_MINTDB_DenominationKeyInformationP +{ + + /** + * Signature over this struct to affirm the validity of the key. + */ + struct TALER_MasterSignatureP signature; + + /** + * Signed properties of the denomination key. + */ + struct TALER_DenominationKeyValidityPS properties; +}; + + +GNUNET_NETWORK_STRUCT_END + + +/** + * @brief All information about a denomination key (which is used to + * sign coins into existence). + */ +struct TALER_MINTDB_DenominationKeyIssueInformation +{ + /** + * The private key of the denomination. Will be NULL if the private + * key is not available (this is the case after the key has expired + * for signing coins, but is still valid for depositing coins). + */ + struct TALER_DenominationPrivateKey denom_priv; + + /** + * Decoded denomination public key (the hash of it is in + * @e issue, but we sometimes need the full public key as well). + */ + struct TALER_DenominationPublicKey denom_pub; + + /** + * Signed public information about a denomination key. + */ + struct TALER_MINTDB_DenominationKeyInformationP issue; +}; + + +/** + * @brief Iterator over signing keys. + * + * @param cls closure + * @param filename name of the file the key came from + * @param ski the sign key + * @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_MINTDB_SigningKeyIterator)(void *cls, + const char *filename, + const struct TALER_MINTDB_PrivateSigningKeyInformationP *ski); + + +/** + * @brief Iterator over denomination keys. + * + * @param cls closure + * @param dki the denomination key + * @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_MINTDB_DenominationKeyIterator)(void *cls, + const char *alias, + const struct TALER_MINTDB_DenominationKeyIssueInformation *dki); + + + +/** + * Call @a it for each signing key found in the @a mint_base_dir. + * + * @param mint_base_dir base directory for the mint, + * the signing keys must be in the #TALER_MINTDB_DIR_SIGNING_KEYS + * subdirectory + * @param it function to call on each signing key + * @param it_cls closure for @a it + * @return number of files found (may not match + * number of keys given to @a it as malformed + * files are simply skipped), -1 on error + */ +int +TALER_MINTDB_signing_keys_iterate (const char *mint_base_dir, + TALER_MINTDB_SigningKeyIterator it, + void *it_cls); + + +/** + * Call @a it for each denomination key found in the @a mint_base_dir. + * + * @param mint_base_dir base directory for the mint, + * the signing keys must be in the #TALER_MINTDB_DIR_DENOMINATION_KEYS + * subdirectory + * @param it function to call on each denomination key found + * @param it_cls closure for @a it + * @return -1 on error, 0 if no files were found, otherwise + * a positive number (however, even with a positive + * number it is possible that @a it was never called + * as maybe none of the files were well-formed) + */ +int +TALER_MINTDB_denomination_keys_iterate (const char *mint_base_dir, + TALER_MINTDB_DenominationKeyIterator it, + void *it_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_MINTDB_denomination_key_write (const char *filename, + const struct TALER_MINTDB_DenominationKeyIssueInformation *dki); + + +/** + * Import a denomination key from the given file. + * + * @param filename the file to import the key from + * @param[out] dki set to the imported denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +int +TALER_MINTDB_denomination_key_read (const char *filename, + struct TALER_MINTDB_DenominationKeyIssueInformation *dki); + + +/** + * Initialize the plugin. + * + * @param cfg configuration to use + * @return NULL on failure + */ +struct TALER_MINTDB_Plugin * +TALER_MINTDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg); + + +/** + * Shutdown the plugin. + * + * @param plugin plugin to unload + */ +void +TALER_MINTDB_plugin_unload (struct TALER_MINTDB_Plugin *plugin); + + + +#endif diff --git a/src/backend/taler_mintdb_plugin.h b/src/backend/taler_mintdb_plugin.h new file mode 100644 index 00000000..21d83d9d --- /dev/null +++ b/src/backend/taler_mintdb_plugin.h @@ -0,0 +1,1218 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/taler_mintdb_plugin.h + * @brief Low-level (statement-level) database access for the mint + * @author Florian Dold + * @author Christian Grothoff + */ +#ifndef TALER_MINTDB_PLUGIN_H +#define TALER_MINTDB_PLUGIN_H + +#include <gnunet/gnunet_util_lib.h> +#include "taler_mintdb_lib.h" + + +/** + * @brief Information we keep on bank transfer(s) that established a reserve. + */ +struct TALER_MINTDB_BankTransfer +{ + + /** + * Public key of the reserve that was filled. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Amount that was transferred to the mint. + */ + struct TALER_Amount amount; + + /** + * When did the mint receive the incoming transaction? + * (This is the execution date of the mint's database, + * the execution date of the bank should be in @e wire). + */ + struct GNUNET_TIME_Absolute execution_date; + + /** + * Detailed wire information about the transaction. + */ + json_t *wire; + +}; + + +/** + * @brief A summary of a Reserve + */ +struct TALER_MINTDB_Reserve +{ + /** + * The reserve's public key. This uniquely identifies the reserve + */ + struct TALER_ReservePublicKeyP pub; + + /** + * The balance amount existing in the reserve + */ + struct TALER_Amount balance; + + /** + * The expiration date of this reserve + */ + struct GNUNET_TIME_Absolute expiry; +}; + + +/** + * @brief Information we keep for a withdrawn coin to reproduce + * the /withdraw operation if needed, and to have proof + * that a reserve was drained by this amount. + */ +struct TALER_MINTDB_CollectableBlindcoin +{ + + /** + * Our signature over the (blinded) coin. + */ + struct TALER_DenominationSignature sig; + + /** + * Denomination key (which coin was generated). + */ + struct TALER_DenominationPublicKey denom_pub; + + /** + * Value of the coin being minted (matching the denomination key) + * plus the transaction fee. We include this in what is being + * signed so that we can verify a reserve's remaining total balance + * without needing to access the respective denomination key + * information each time. + */ + struct TALER_Amount amount_with_fee; + + /** + * Withdrawl fee charged by the mint. This must match the Mint's + * denomination key's withdrawl fee. If the client puts in an + * invalid withdrawl fee (too high or too low) that does not match + * the Mint's denomination key, the withdraw operation is invalid + * and will be rejected by the mint. The @e amount_with_fee minus + * the @e withdraw_fee is must match the value of the generated + * coin. We include this in what is being signed so that we can + * verify a mint's accounting without needing to access the + * respective denomination key information each time. + */ + struct TALER_Amount withdraw_fee; + + /** + * Public key of the reserve that was drained. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Hash over the blinded message, needed to verify + * the @e reserve_sig. + */ + struct GNUNET_HashCode h_coin_envelope; + + /** + * Signature confirming the withdrawl, matching @e reserve_pub, + * @e denom_pub and @e h_coin_envelope. + */ + struct TALER_ReserveSignatureP reserve_sig; +}; + + + +/** + * @brief Types of operations on a reserved. + */ +enum TALER_MINTDB_ReserveOperation +{ + /** + * Money was deposited into the reserve via a bank transfer. + */ + TALER_MINTDB_RO_BANK_TO_MINT = 0, + + /** + * A Coin was withdrawn from the reserve using /withdraw. + */ + TALER_MINTDB_RO_WITHDRAW_COIN = 1 +}; + + +/** + * @brief Reserve history as a linked list. Lists all of the transactions + * associated with this reserve (such as the bank transfers that + * established the reserve and all /withdraw operations we have done + * since). + */ +struct TALER_MINTDB_ReserveHistory +{ + + /** + * Next entry in the reserve history. + */ + struct TALER_MINTDB_ReserveHistory *next; + + /** + * Type of the event, determins @e details. + */ + enum TALER_MINTDB_ReserveOperation type; + + /** + * Details of the operation, depending on @e type. + */ + union + { + + /** + * Details about a bank transfer to the mint. + */ + struct TALER_MINTDB_BankTransfer *bank; + + /** + * Details about a /withdraw operation. + */ + struct TALER_MINTDB_CollectableBlindcoin *withdraw; + + } details; + +}; + + +/** + * @brief Specification for a /deposit operation. The combination of + * the coin's public key, the merchant's public key and the + * transaction ID must be unique. While a coin can (theoretically) be + * deposited at the same merchant twice (with partial spending), the + * merchant must either use a different public key or a different + * transaction ID for the two transactions. The same coin must not + * be used twice at the same merchant for the same transaction + * (as determined by transaction ID). (Note: we might want to + * fix #3819 and include at least h_contract as well.) + */ +struct TALER_MINTDB_Deposit +{ + /** + * Information about the coin that is being deposited. + */ + struct TALER_CoinPublicInfo coin; + + /** + * ECDSA signature affirming that the customer intends + * this coin to be deposited at the merchant identified + * by @e h_wire in relation to the contract identified + * by @e h_contract. + */ + struct TALER_CoinSpendSignatureP csig; + + /** + * Public key of the merchant. Enables later identification + * of the merchant in case of a need to rollback transactions. + */ + struct TALER_MerchantPublicKeyP merchant_pub; + + /** + * Hash over the contract between merchant and customer + * (remains unknown to the Mint). + */ + struct GNUNET_HashCode h_contract; + + /** + * Hash of the (canonical) representation of @e wire, used + * to check the signature on the request. Generated by + * the mint from the detailed wire data provided by the + * merchant. + */ + struct GNUNET_HashCode h_wire; + + /** + * Detailed wire information for executing the transaction. + */ + json_t *wire; + + /** + * Merchant-generated transaction ID to detect duplicate + * transactions. + */ + uint64_t transaction_id; + + /** + * Time when this request was generated. Used, for example, to + * assess when (roughly) the income was achieved for tax purposes. + * Note that the Mint will only check that the timestamp is not "too + * far" into the future (i.e. several days). The fact that the + * timestamp falls within the validity period of the coin's + * denomination key is irrelevant for the validity of the deposit + * request, as obviously the customer and merchant could conspire to + * set any timestamp. Also, the Mint must accept very old deposit + * requests, as the merchant might have been unable to transmit the + * deposit request in a timely fashion (so back-dating is not + * prevented). + */ + struct GNUNET_TIME_Absolute timestamp; + + /** + * How much time does the merchant have to issue a refund request? + * Zero if refunds are not allowed. After this time, the coin + * cannot be refunded. + */ + struct GNUNET_TIME_Absolute refund_deadline; + + /** + * Fraction of the coin's remaining value to be deposited, including + * depositing fee (if any). The coin is identified by @e coin_pub. + */ + struct TALER_Amount amount_with_fee; + + /** + * Depositing fee. + */ + struct TALER_Amount deposit_fee; + +}; + + +/** + * @brief Global information for a refreshing session. Includes + * dimensions of the operation, security parameters and + * client signatures from "/refresh/melt" and "/refresh/commit". + */ +struct TALER_MINTDB_RefreshSession +{ + + /** + * Number of coins we are melting. + */ + uint16_t num_oldcoins; + + /** + * Number of new coins we are creating. + */ + uint16_t num_newcoins; + + /** + * Index (smaller #TALER_CNC_KAPPA) which the mint has chosen to not + * have revealed during cut and choose. + */ + uint16_t noreveal_index; + +}; + + +/** + * @brief Specification for coin in a /refresh/melt operation. + */ +struct TALER_MINTDB_RefreshMelt +{ + /** + * Information about the coin that is being melted. + */ + struct TALER_CoinPublicInfo coin; + + /** + * Signature over the melting operation. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** + * Hash of the refresh session this coin is melted into. + */ + struct GNUNET_HashCode session_hash; + + /** + * How much value is being melted? This amount includes the fees, + * so the final amount contributed to the melt is this value minus + * the fee for melting the coin. We include the fee in what is + * being signed so that we can verify a reserve's remaining total + * balance without needing to access the respective denomination key + * information each time. + */ + struct TALER_Amount amount_with_fee; + + /** + * Melting fee charged by the mint. This must match the Mint's + * denomination key's melting fee. If the client puts in an invalid + * melting fee (too high or too low) that does not match the Mint's + * denomination key, the melting operation is invalid and will be + * rejected by the mint. The @e amount_with_fee minus the @e + * melt_fee is the amount that will be credited to the melting + * session. + */ + struct TALER_Amount melt_fee; + +}; + + +/** + * @brief We have as many `struct TALER_MINTDB_RefreshCommitCoin` as there are new + * coins being created by the refresh (for each of the #TALER_CNC_KAPPA + * sets). These are the coins we ask the mint to sign if the + * respective set is selected. + */ +struct TALER_MINTDB_RefreshCommitCoin +{ + + /** + * Encrypted data allowing those able to decrypt it to derive + * the private keys of the new coins created by the refresh. + */ + struct TALER_RefreshLinkEncrypted *refresh_link; + + /** + * Blinded message to be signed (in envelope), with @e coin_env_size bytes. + */ + char *coin_ev; + + /** + * Number of bytes in @e coin_ev. + */ + size_t coin_ev_size; + +}; + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief For each (old) coin being melted, we have a `struct + * RefreshCommitLinkP` that allows the user to find the shared secret + * to decrypt the respective refresh links for the new coins in the + * `struct TALER_MINTDB_RefreshCommitCoin`. + */ +struct TALER_MINTDB_RefreshCommitLinkP +{ + /** + * Transfer public key, used to decrypt the @e shared_secret_enc + * in combintation with the corresponding private key of the + * coin. + */ + struct TALER_TransferPublicKeyP transfer_pub; + + /** + * Encrypted shared secret to decrypt the link. + */ + struct TALER_EncryptedLinkSecretP shared_secret_enc; +}; + +GNUNET_NETWORK_STRUCT_END + + + +/** + * @brief Linked list of refresh information linked to a coin. + */ +struct TALER_MINTDB_LinkDataList +{ + /** + * Information is stored in a NULL-terminated linked list. + */ + struct TALER_MINTDB_LinkDataList *next; + + /** + * Link data, used to recover the private key of the coin + * by the owner of the old coin. + */ + struct TALER_RefreshLinkEncrypted *link_data_enc; + + /** + * Denomination public key, determines the value of the coin. + */ + struct TALER_DenominationPublicKey denom_pub; + + /** + * Signature over the blinded envelope. + */ + struct TALER_DenominationSignature ev_sig; +}; + + +/** + * @brief Specification for a /lock operation. + */ +struct TALER_MINTDB_LockOperation +{ + /** + * Information about the coin that is being locked. + */ + struct TALER_CoinPublicInfo coin; + + /** + * Signature over the locking operation. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** + * How much value is being locked? + */ + struct TALER_Amount amount; + + // FIXME: more needed... +}; + + +/** + * @brief Enumeration to classify the different types of transactions + * that can be done with a coin. + */ +enum TALER_MINTDB_TransactionType +{ + /** + * /deposit operation. + */ + TALER_MINTDB_TT_DEPOSIT = 0, + + /** + * /refresh/melt operation. + */ + TALER_MINTDB_TT_REFRESH_MELT = 1, + + /** + * /lock operation. + */ + TALER_MINTDB_TT_LOCK = 2 +}; + + +/** + * @brief List of transactions we performed for a particular coin. + */ +struct TALER_MINTDB_TransactionList +{ + + /** + * Next pointer in the NULL-terminated linked list. + */ + struct TALER_MINTDB_TransactionList *next; + + /** + * Type of the transaction, determines what is stored in @e details. + */ + enum TALER_MINTDB_TransactionType type; + + /** + * Details about the transaction, depending on @e type. + */ + union + { + + /** + * Details if transaction was a /deposit operation. + */ + struct TALER_MINTDB_Deposit *deposit; + + /** + * Details if transaction was a /refresh/melt operation. + */ + struct TALER_MINTDB_RefreshMelt *melt; + + /** + * Details if transaction was a /lock operation. + */ + struct TALER_MINTDB_LockOperation *lock; + + } details; + +}; + + +/** + * @brief All of the information from a /refresh/melt commitment. + */ +struct TALER_MINTDB_MeltCommitment +{ + + /** + * Number of coins we are melting. + */ + uint16_t num_oldcoins; + + /** + * Number of new coins we are creating. + */ + uint16_t num_newcoins; + + /** + * Array of @e num_oldcoins melt operation details. + */ + struct TALER_MINTDB_RefreshMelt *melts; + + /** + * Array of @e num_newcoins denomination keys + */ + struct TALER_DenominationPublicKey *denom_pubs; + + /** + * 2D-Array of #TALER_CNC_KAPPA and @e num_newcoins commitments. + */ + struct TALER_MINTDB_RefreshCommitCoin *commit_coins[TALER_CNC_KAPPA]; + + /** + * 2D-Array of #TALER_CNC_KAPPA and @e new_oldcoins links. + */ + struct TALER_MINTDB_RefreshCommitLinkP *commit_links[TALER_CNC_KAPPA]; +}; + + +/** + * @brief Handle for a database session (per-thread, for transactions). + */ +struct TALER_MINTDB_Session; + + +/** + * Function called with the session hashes and transfer secret + * information for a given coin. + * + * @param cls closure + * @param session_hash a session the coin was melted in + * @param transfer_pub public transfer key for the session + * @param shared_secret_enc set to shared secret for the session + */ +typedef void +(*TALER_MINTDB_TransferDataCallback)(void *cls, + const struct GNUNET_HashCode *session_hash, + const struct TALER_TransferPublicKeyP *transfer_pub, + const struct TALER_EncryptedLinkSecretP *shared_secret_enc); + + +/** + * @brief The plugin API, returned from the plugin's "init" function. + * The argument given to "init" is simply a configuration handle. + */ +struct TALER_MINTDB_Plugin +{ + + /** + * Closure for all callbacks. + */ + void *cls; + + /** + * Name of the library which generated this plugin. Set by the + * plugin loader. + */ + char *library_name; + + /** + * Get the thread-local database-handle. + * Connect to the db if the connection does not exist yet. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param temporary #GNUNET_YES to use a temporary schema; #GNUNET_NO to use the + * database default one + * @param the database connection, or NULL on error + */ + struct TALER_MINTDB_Session * + (*get_session) (void *cls, + int temporary); + + + /** + * Drop the temporary taler schema. This is only useful for testcases. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ + int + (*drop_temporary) (void *cls, + struct TALER_MINTDB_Session *db); + + + /** + * Create the necessary tables if they are not present + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param temporary should we use a temporary schema + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ + int + (*create_tables) (void *cls, + int temporary); + + + /** + * Start a transaction. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param session connection to use + * @return #GNUNET_OK on success + */ + int + (*start) (void *cls, + struct TALER_MINTDB_Session *session); + + + /** + * Commit a transaction. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion connection to use + * @return #GNUNET_OK on success + */ + int + (*commit) (void *cls, + struct TALER_MINTDB_Session *sesssion); + + + /** + * Abort/rollback a transaction. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion connection to use + */ + void + (*rollback) (void *cls, + struct TALER_MINTDB_Session *sesssion); + + + /** + * Insert information about a denomination key and in particular + * the properties (value, fees, expiration times) the coins signed + * with this key have. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion connection to use + * @param denom_pub the public key used for signing coins of this denomination + * @param issue issuing information with value, fees and other info about the coin + * @return #GNUNET_OK on success; #GNUNET_SYSERR on failure + */ + int + (*insert_denomination_info) (void *cls, + struct TALER_MINTDB_Session *session, + const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_MINTDB_DenominationKeyInformationP *issue); + + + /** + * Fetch information about a denomination key. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion connection to use + * @param denom_pub the public key used for signing coins of this denomination + * @param[out] issue set to issue information with value, fees and other info about the coin, can be NULL + * @return #GNUNET_OK on success; #GNUNET_NO if no record was found, #GNUNET_SYSERR on failure + */ + int + (*get_denomination_info) (void *cls, + struct TALER_MINTDB_Session *session, + const struct TALER_DenominationPublicKey *denom_pub, + struct TALER_MINTDB_DenominationKeyInformationP *issue); + + + /** + * Get the summary of a reserve. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db the database connection handle + * @param[in,out] reserve the reserve data. The public key of the reserve should be set + * in this structure; it is used to query the database. The balance + * and expiration are then filled accordingly. + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ + int + (*reserve_get) (void *cls, + struct TALER_MINTDB_Session *db, + struct TALER_MINTDB_Reserve *reserve); + + + /** + * Insert a incoming transaction into reserves. New reserves are + * also created through this function. Note that this API call + * starts (and stops) its own transaction scope (so the application + * must not do so). + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db the database connection handle + * @param reserve_pub public key of the reserve + * @param balance the amount that has to be added to the reserve + * @param execution_time when was the amount added + * @param details bank transaction details justifying the increment, + * must be unique for each incoming transaction + * @return #GNUNET_OK upon success; #GNUNET_NO if the given + * @a details are already known for this @a reserve_pub, + * #GNUNET_SYSERR upon failures (DB error, incompatible currency) + */ + int + (*reserves_in_insert) (void *cls, + struct TALER_MINTDB_Session *db, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *balance, + struct GNUNET_TIME_Absolute execution_time, + const json_t *details); + + + /** + * Locate the response for a /withdraw request under the + * key of the hash of the blinded message. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection to use + * @param h_blind hash of the blinded coin to be signed (will match + * `h_coin_envelope` in the @a collectable to be returned) + * @param collectable corresponding collectable coin (blind signature) + * if a coin is found + * @return #GNUNET_SYSERR on internal error + * #GNUNET_NO if the collectable was not found + * #GNUNET_YES on success + */ + int + (*get_withdraw_info) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct GNUNET_HashCode *h_blind, + struct TALER_MINTDB_CollectableBlindcoin *collectable); + + + /** + * Store collectable bit coin under the corresponding + * hash of the blinded message. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection to use + * @param collectable corresponding collectable coin (blind signature) + * if a coin is found + * @return #GNUNET_SYSERR on internal error + * #GNUNET_NO if the collectable was not found + * #GNUNET_YES on success + */ + int + (*insert_withdraw_info) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct TALER_MINTDB_CollectableBlindcoin *collectable); + + + /** + * Get all of the transaction history associated with the specified + * reserve. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion connection to use + * @param reserve_pub public key of the reserve + * @return known transaction history (NULL if reserve is unknown) + */ + struct TALER_MINTDB_ReserveHistory * + (*get_reserve_history) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct TALER_ReservePublicKeyP *reserve_pub); + + + /** + * Free memory associated with the given reserve history. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param rh history to free. + */ + void + (*free_reserve_history) (void *cls, + struct TALER_MINTDB_ReserveHistory *rh); + + + /** + * Check if we have the specified deposit already in the database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection + * @param deposit deposit to search for + * @return #GNUNET_YES if we know this operation, + * #GNUNET_NO if this deposit is unknown to us, + * #GNUNET_SYSERR on DB error or if same coin(pub), merchant(pub) and + * transaction ID are already in DB, but for different + * other transaction details (contract, wiring details, + * amount, etc.) + */ + int + (*have_deposit) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct TALER_MINTDB_Deposit *deposit); + + + /** + * Insert information about deposited coin into the + * database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion connection to the database + * @param deposit deposit information to store + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ + int + (*insert_deposit) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct TALER_MINTDB_Deposit *deposit); + + + /** + * Lookup refresh session data under the given @a session_hash. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database handle to use + * @param session_hash hash over the melt to use for the lookup + * @param[out] refresh_session where to store the result + * @return #GNUNET_YES on success, + * #GNUNET_NO if not found, + * #GNUNET_SYSERR on DB failure + */ + int + (*get_refresh_session) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct GNUNET_HashCode *session_hash, + struct TALER_MINTDB_RefreshSession *refresh_session); + + + /** + * Store new refresh session data under the given @a session_hash. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database handle to use + * @param session_hash hash over the melt to use to locate the session + * @param refresh_session session data to store + * @return #GNUNET_YES on success, + * #GNUNET_SYSERR on DB failure + */ + int + (*create_refresh_session) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct GNUNET_HashCode *session_hash, + const struct TALER_MINTDB_RefreshSession *refresh_session); + + + /** + * Store the given /refresh/melt request in the database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection + * @param oldcoin_index index of the coin to store + * @param melt coin melt operation details to store; includes + * the session hash of the melt + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ + int + (*insert_refresh_melt) (void *cls, + struct TALER_MINTDB_Session *sesssion, + uint16_t oldcoin_index, + const struct TALER_MINTDB_RefreshMelt *melt); + + + /** + * Get information about melted coin details from the database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection + * @param session_hash hash to identify refresh session + * @param oldcoin_index index of the coin to retrieve + * @param melt melt data to fill in, can be NULL + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ + int + (*get_refresh_melt) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct GNUNET_HashCode *session_hash, + uint16_t oldcoin_index, + struct TALER_MINTDB_RefreshMelt *melt); + + + /** + * Store in the database which coin(s) we want to create + * in a given refresh operation. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection + * @param session_hash hash to identify refresh session + * @param num_newcoins number of coins to generate, size of the @a denom_pubs array + * @param denom_pubs array denominations of the coins to create + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ + int + (*insert_refresh_order) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct GNUNET_HashCode *session_hash, + uint16_t num_newcoins, + const struct TALER_DenominationPublicKey *denom_pubs); + + + /** + * Lookup in the database for the @a num_newcoins coins that we want to + * create in the given refresh operation. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection + * @param session_hash hash to identify refresh session + * @param num_newcoins size of the @a denom_pubs array + * @param[out] denom_pubs where to write @a num_newcoins denomination keys + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ + int + (*get_refresh_order) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct GNUNET_HashCode *session_hash, + uint16_t num_newcoins, + struct TALER_DenominationPublicKey *denom_pubs); + + + /** + * Store information about the commitments of the given index @a i + * for the given refresh session in the database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection to use + * @param session_hash hash to identify refresh session + * @param cnc_index cut and choose index (1st dimension), relating to #TALER_CNC_KAPPA + * @param num_newcoins coin index size of the @a commit_coins array + * @param commit_coin array of coin commitments to store + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on error + */ + int + (*insert_refresh_commit_coins) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct GNUNET_HashCode *session_hash, + uint16_t cnc_index, + uint16_t num_newcoins, + const struct TALER_MINTDB_RefreshCommitCoin *commit_coins); + + + /** + * Obtain information about the commitment of the + * given coin of the given refresh session from the database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection to use + * @param session_hash hash to identify refresh session + * @param cnc_index cut and choose set index (1st dimension) + * @param num_coins size of the @a commit_coins array + * @param[out] commit_coins array of coin commitments to return + * @return #GNUNET_OK on success + * #GNUNET_NO if not found + * #GNUNET_SYSERR on error + */ + int + (*get_refresh_commit_coins) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct GNUNET_HashCode *session_hash, + uint16_t cnc_index, + uint16_t num_coins, + struct TALER_MINTDB_RefreshCommitCoin *commit_coins); + + + /** + * Store the commitment to the given (encrypted) refresh link data + * for the given refresh session. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection to use + * @param session_hash hash to identify refresh session + * @param cnc_index cut and choose index (1st dimension), relating to #TALER_CNC_KAPPA + * @param num_links size of the @a commit_link array + * @param commit_links array of link information to store + * @return #GNUNET_SYSERR on internal error, #GNUNET_OK on success + */ + int + (*insert_refresh_commit_links) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct GNUNET_HashCode *session_hash, + uint16_t cnc_index, + uint16_t num_links, + const struct TALER_MINTDB_RefreshCommitLinkP *commit_links); + + /** + * Obtain the commited (encrypted) refresh link data + * for the given refresh session. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection to use + * @param session_hash hash to identify refresh session + * @param cnc_index cut and choose index (1st dimension) + * @param num_links size of the @a links array to return + * @param[out] links array link information to return + * @return #GNUNET_SYSERR on internal error, + * #GNUNET_NO if commitment was not found + * #GNUNET_OK on success + */ + int + (*get_refresh_commit_links) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct GNUNET_HashCode *session_hash, + uint16_t cnc_index, + uint16_t num_links, + struct TALER_MINTDB_RefreshCommitLinkP *links); + + + /** + * Get all of the information from the given melt commit operation. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection to use + * @param session_hash hash to identify refresh session + * @return NULL if the @a session_hash does not correspond to any known melt + * operation + */ + struct TALER_MINTDB_MeltCommitment * + (*get_melt_commitment) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct GNUNET_HashCode *session_hash); + + + /** + * Free information about a melt commitment. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param mc melt commitment data to free + */ + void + (*free_melt_commitment) (void *cls, + struct TALER_MINTDB_MeltCommitment *mc); + + + /** + * Insert signature of a new coin generated during refresh into + * the database indexed by the refresh session and the index + * of the coin. This data is later used should an old coin + * be used to try to obtain the private keys during "/refresh/link". + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection + * @param session_hash hash to identify refresh session + * @param newcoin_index coin index + * @param ev_sig coin signature + * @return #GNUNET_OK on success + */ + int + (*insert_refresh_out) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct GNUNET_HashCode *session_hash, + uint16_t newcoin_index, + const struct TALER_DenominationSignature *ev_sig); + + + /** + * Obtain the link data of a coin, that is the encrypted link + * information, the denomination keys and the signatures. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection + * @param session_hash session to get linkage data for + * @return all known link data for the session + */ + struct TALER_MINTDB_LinkDataList * + (*get_link_data_list) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct GNUNET_HashCode *session_hash); + + + /** + * Free memory of the link data list. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param ldl link data list to release + */ + void + (*free_link_data_list) (void *cls, + struct TALER_MINTDB_LinkDataList *ldl); + + + /** + * Obtain shared secret and transfer public key from the public key of + * the coin. This information and the link information returned by + * @e get_link_data_list() enable the owner of an old coin to determine + * the private keys of the new coins after the melt. + * + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection + * @param coin_pub public key of the coin + * @param tdc function to call for each session the coin was melted into + * @param tdc_cls closure for @a tdc + * @return #GNUNET_OK on success, + * #GNUNET_NO on failure (not found) + * #GNUNET_SYSERR on internal failure (database issue) + */ + int + (*get_transfer) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + TALER_MINTDB_TransferDataCallback tdc, + void *tdc_cls); + + + + /** + * Test if the given /lock request is known to us. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection + * @param lock lock operation + * @return #GNUNET_YES if known, + * #GNUNET_NO if not, + * #GNUNET_SYSERR on internal error + */ + int + (*have_lock) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct TALER_MINTDB_LockOperation *lock); + + + /** + * Store the given /lock request in the database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection + * @param lock lock operation + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ + int + (*insert_lock) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct TALER_MINTDB_LockOperation *lock); + + + /** + * Compile a list of all (historic) transactions performed + * with the given coin (/refresh/melt and /deposit operations). + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param sesssion database connection + * @param coin_pub coin to investigate + * @return list of transactions, NULL if coin is fresh + */ + struct TALER_MINTDB_TransactionList * + (*get_coin_transactions) (void *cls, + struct TALER_MINTDB_Session *sesssion, + const struct TALER_CoinSpendPublicKeyP *coin_pub); + + + /** + * Free linked list of transactions. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param list list to free + */ + void + (*free_coin_transaction_list) (void *cls, + struct TALER_MINTDB_TransactionList *list); + + +}; + + +#endif /* _NEURO_MINT_DB_H */ diff --git a/src/backend/taler_signatures.h b/src/backend/taler_signatures.h new file mode 100644 index 00000000..402e67fe --- /dev/null +++ b/src/backend/taler_signatures.h @@ -0,0 +1,653 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler_signatures.h + * @brief message formats and signature constants used to define + * the binary formats of signatures in Taler + * @author Florian Dold + * @author Benedikt Mueller + * + * This file should define the constants and C structs that one needs + * to know to implement Taler clients (wallets or merchants or + * auditor) that need to produce or verify Taler signatures. + */ + +#ifndef TALER_SIGNATURES_H +#define TALER_SIGNATURES_H + +#if HAVE_GNUNET_GNUNET_UTIL_LIB_H +#include <gnunet/gnunet_util_lib.h> +#elif HAVE_GNUNET_GNUNET_UTIL_TALER_WALLET_LIB_H +#include <gnunet/gnunet_util_taler_wallet_lib.h> +#endif + +#include "taler_amount_lib.h" +#include "taler_crypto_lib.h" + +/** + * Cut-and-choose size for refreshing. Client looses the gamble (of + * unaccountable transfers) with probability 1/TALER_CNC_KAPPA. Refresh cost + * increases linearly with TALER_CNC_KAPPA, and 3 is sufficient up to a + * income/sales tax of 66% of total transaction value. As there is + * no good reason to change this security parameter, we declare it + * fixed and part of the protocol. + */ +#define TALER_CNC_KAPPA 3 + +/** + * After what time do idle reserves "expire"? We might want to make + * this a configuration option (eventually). + */ +#define TALER_IDLE_RESERVE_EXPIRATION_TIME GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS, 5) + +/*********************************************/ +/* Mint offline signatures (with master key) */ +/*********************************************/ + +/** + * Purpose for signing public keys signed by the mint master key. + */ +#define TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY 1024 + +/** + * Purpose for denomination keys signed by the mint master key. + */ +#define TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY 1025 + + +/*********************************************/ +/* Mint online signatures (with signing key) */ +/*********************************************/ + +/** + * Purpose for the state of a reserve, signed by the mint's signing + * key. + */ +#define TALER_SIGNATURE_MINT_RESERVE_STATUS 1032 + +/** + * Signature where the Mint confirms a deposit request. + */ +#define TALER_SIGNATURE_MINT_CONFIRM_DEPOSIT 1033 + +/** + * Signature where the mint (current signing key) confirms the + * no-reveal index for cut-and-choose and the validity of the melted + * coins. + */ +#define TALER_SIGNATURE_MINT_CONFIRM_MELT 1034 + +/** + * Signature where the Mint confirms the full /keys response set. + */ +#define TALER_SIGNATURE_MINT_KEY_SET 1035 + + +/*********************/ +/* Wallet signatures */ +/*********************/ + +/** + * Signature where the auditor confirms that he is + * aware of certain denomination keys from the mint. + */ +#define TALER_SIGNATURE_AUDITOR_MINT_KEYS 1064 + + +/***********************/ +/* Merchant signatures */ +/***********************/ + +/** + * Signature where the merchant confirms a contract (to the customer). + */ +#define TALER_SIGNATURE_MERCHANT_CONTRACT 1101 + +/** + * Signature where the merchant confirms a refund (of a coin). + */ +#define TALER_SIGNATURE_MERCHANT_REFUND 1102 + + +/*********************/ +/* Wallet signatures */ +/*********************/ + +/** + * Signature where the reserve key confirms a withdraw request. + */ +#define TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW 1200 + +/** + * Signature made by the wallet of a user to confirm a deposit of a coin. + */ +#define TALER_SIGNATURE_WALLET_COIN_DEPOSIT 1201 + +/** + * Signature using a coin key confirming the melting of a coin. + */ +#define TALER_SIGNATURE_WALLET_COIN_MELT 1202 + + +/*******************/ +/* Test signatures */ +/*******************/ + +/** + * EdDSA test signature. + */ +#define TALER_SIGNATURE_CLIENT_TEST_EDDSA 1302 + +/** + * EdDSA test signature. + */ +#define TALER_SIGNATURE_MINT_TEST_EDDSA 1303 + + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format used for to generate the signature on a request to withdraw + * coins from a reserve. + */ +struct TALER_WithdrawRequestPS +{ + + /** + * Purpose must be #TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW. + * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Reserve public key (which reserve to withdraw from). This is + * the public key which must match the signature. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Value of the coin being minted (matching the denomination key) + * plus the transaction fee. We include this in what is being + * signed so that we can verify a reserve's remaining total balance + * without needing to access the respective denomination key + * information each time. + */ + struct TALER_AmountNBO amount_with_fee; + + /** + * Withdrawl fee charged by the mint. This must match the Mint's + * denomination key's withdrawl fee. If the client puts in an + * invalid withdrawl fee (too high or too low) that does not match + * the Mint's denomination key, the withdraw operation is invalid + * and will be rejected by the mint. The @e amount_with_fee minus + * the @e withdraw_fee is must match the value of the generated + * coin. We include this in what is being signed so that we can + * verify a mint's accounting without needing to access the + * respective denomination key information each time. + */ + struct TALER_AmountNBO withdraw_fee; + + /** + * Hash of the denomination public key for the coin that is withdrawn. + */ + struct GNUNET_HashCode h_denomination_pub; + + /** + * Hash of the (blinded) message to be signed by the Mint. + */ + struct GNUNET_HashCode h_coin_envelope; +}; + + +/** + * @brief Format used to generate the signature on a request to deposit + * a coin into the account of a merchant. + */ +struct TALER_DepositRequestPS +{ + /** + * Purpose must be #TALER_SIGNATURE_WALLET_COIN_DEPOSIT. + * Used for an EdDSA signature with the `struct TALER_CoinSpendPublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the contract for which this deposit is made. + */ + struct GNUNET_HashCode h_contract; + + /** + * Hash over the wiring information of the merchant. + */ + struct GNUNET_HashCode h_wire; + + /** + * Time when this request was generated. Used, for example, to + * assess when (roughly) the income was achieved for tax purposes. + * Note that the Mint will only check that the timestamp is not "too + * far" into the future (i.e. several days). The fact that the + * timestamp falls within the validity period of the coin's + * denomination key is irrelevant for the validity of the deposit + * request, as obviously the customer and merchant could conspire to + * set any timestamp. Also, the Mint must accept very old deposit + * requests, as the merchant might have been unable to transmit the + * deposit request in a timely fashion (so back-dating is not + * prevented). + */ + struct GNUNET_TIME_AbsoluteNBO timestamp; + + /** + * How much time does the merchant have to issue a refund request? + * Zero if refunds are not allowed. After this time, the coin + * cannot be refunded. + */ + struct GNUNET_TIME_AbsoluteNBO refund_deadline; + + /** + * Merchant-generated transaction ID to detect duplicate + * transactions. The merchant must communicate a merchant-unique ID + * to the customer for each transaction. Note that different coins + * that are part of the same transaction can use the same + * transaction ID. The transaction ID is useful for later disputes, + * and the merchant's contract offer (@e h_contract) with the + * customer should include the offer's term and transaction ID + * signed with a key from the merchant. + */ + uint64_t transaction_id GNUNET_PACKED; + + /** + * Amount to be deposited, including deposit fee charged by the + * mint. This is the total amount that the coin's value at the mint + * will be reduced by. + */ + struct TALER_AmountNBO amount_with_fee; + + /** + * Depositing fee charged by the mint. This must match the Mint's + * denomination key's depositing fee. If the client puts in an + * invalid deposit fee (too high or too low) that does not match the + * Mint's denomination key, the deposit operation is invalid and + * will be rejected by the mint. The @e amount_with_fee minus the + * @e deposit_fee is the amount that will be transferred to the + * account identified by @e h_wire. + */ + struct TALER_AmountNBO deposit_fee; + + /** + * The Merchant's public key. Allows the merchant to later refund + * the transaction. All zeros if nobody is allowed to refund the + * transaction later. + */ + struct TALER_MerchantPublicKeyP merchant; + + /** + * The coin's public key. This is the value that must have been + * signed (blindly) by the Mint. The deposit request is to be + * signed by the corresponding private key (using EdDSA). + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + +}; + + +/** + * @brief Format used to generate the signature on a confirmation + * from the mint that a deposit request succeeded. + */ +struct TALER_DepositConfirmationPS +{ + /** + * Purpose must be #TALER_SIGNATURE_MINT_CONFIRM_DEPOSIT. Signed + * by a `struct TALER_MintPublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the contract for which this deposit is made. + */ + struct GNUNET_HashCode h_contract; + + /** + * Hash over the wiring information of the merchant. + */ + struct GNUNET_HashCode h_wire; + + /** + * Merchant-generated transaction ID to detect duplicate + * transactions. + */ + uint64_t transaction_id GNUNET_PACKED; + + /** + * Time when this confirmation was generated. + */ + struct GNUNET_TIME_AbsoluteNBO timestamp; + + /** + * How much time does the @e merchant have to issue a refund + * request? Zero if refunds are not allowed. After this time, the + * coin cannot be refunded. Note that the wire transfer will not be + * performed by the mint until the refund deadline. This value + * is taken from the original deposit request. + */ + struct GNUNET_TIME_AbsoluteNBO refund_deadline; + + /** + * Amount to be deposited, excluding fee. Calculated from the + * amount with fee and the fee from the deposit request. + */ + struct TALER_AmountNBO amount_without_fee; + + /** + * The coin's public key. This is the value that must have been + * signed (blindly) by the Mint. The deposit request is to be + * signed by the corresponding private key (using EdDSA). + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * The Merchant's public key. Allows the merchant to later refund + * the transaction. All zeros if nobody is allowed to refund the + * transaction later. + */ + struct TALER_MerchantPublicKeyP merchant; + +}; + + +/** + * @brief Message signed by a coin to indicate that the coin should be + * melted. + */ +struct TALER_RefreshMeltCoinAffirmationPS +{ + /** + * Purpose is #TALER_SIGNATURE_WALLET_COIN_MELT. + * Used for an EdDSA signature with the `struct TALER_CoinSpendPublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Which melting session should the coin become a part of. + */ + struct GNUNET_HashCode session_hash; + + /** + * How much of the value of the coin should be melted? This amount + * includes the fees, so the final amount contributed to the melt is + * this value minus the fee for melting the coin. We include the + * fee in what is being signed so that we can verify a reserve's + * remaining total balance without needing to access the respective + * denomination key information each time. + */ + struct TALER_AmountNBO amount_with_fee; + + /** + * Melting fee charged by the mint. This must match the Mint's + * denomination key's melting fee. If the client puts in an invalid + * melting fee (too high or too low) that does not match the Mint's + * denomination key, the melting operation is invalid and will be + * rejected by the mint. The @e amount_with_fee minus the @e + * melt_fee is the amount that will be credited to the melting + * session. + */ + struct TALER_AmountNBO melt_fee; + + /** + * The coin's public key. This is the value that must have been + * signed (blindly) by the Mint. The deposit request is to be + * signed by the corresponding private key (using EdDSA). + */ + struct TALER_CoinSpendPublicKeyP coin_pub; +}; + + +/** + * @brief Format of the block signed by the Mint in response to a successful + * "/refresh/melt" request. Hereby the mint affirms that all of the + * coins were successfully melted. This also commits the mint to a + * particular index to not be revealed during the refresh. + */ +struct TALER_RefreshMeltConfirmationPS +{ + /** + * Purpose is #TALER_SIGNATURE_MINT_CONFIRM_MELT. Signed + * by a `struct TALER_MintPublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash of the refresh session. + */ + struct GNUNET_HashCode session_hash; + + /** + * Index that the client will not have to reveal, in NBO. + * Must be smaller than #TALER_CNC_KAPPA. + */ + uint16_t noreveal_index GNUNET_PACKED; +}; + + +/** + * @brief Information about a signing key of the mint. Signing keys are used + * to sign mint messages other than coins, i.e. to confirm that a + * deposit was successful or that a refresh was accepted. + */ +struct TALER_MintSigningKeyValidityPS +{ + /** + * Signature over the signing key (by the master key of the mint). + * + * FIXME: should be moved outside of the "PS" struct, this is ugly. + * (and makes this struct different from all of the others) + */ + struct TALER_MasterSignatureP signature; + + /** + * Purpose is #TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Master public key of the mint corresponding to @e signature. + * This is the long-term offline master key of the mint. + */ + struct TALER_MasterPublicKeyP master_public_key; + + /** + * When does this signing key begin to be valid? + */ + struct GNUNET_TIME_AbsoluteNBO start; + + /** + * When does this signing key expire? Note: This is currently when + * the Mint will definitively stop using it. Signatures made with + * the key remain valid until @e end. When checking validity periods, + * clients should allow for some overlap between keys and tolerate + * the use of either key during the overlap time (due to the + * possibility of clock skew). + */ + struct GNUNET_TIME_AbsoluteNBO expire; + + /** + * When do signatures with this signing key become invalid? After + * this point, these signatures cannot be used in (legal) disputes + * anymore, as the Mint is then allowed to destroy its side of the + * evidence. @e end is expected to be significantly larger than @e + * expire (by a year or more). + */ + struct GNUNET_TIME_AbsoluteNBO end; + + /** + * The public online signing key that the mint will use + * between @e start and @e expire. + */ + struct TALER_MintPublicKeyP signkey_pub; +}; + + +/** + * @brief Signature made by the mint over the full set of keys, used + * to detect cheating mints that give out different sets to + * different users. + */ +struct TALER_MintKeySetPS +{ + + /** + * Purpose is #TALER_SIGNATURE_MINT_KEY_SET. Signed + * by a `struct TALER_MintPublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Time of the key set issue. + */ + struct GNUNET_TIME_AbsoluteNBO list_issue_date; + + /** + * Hash over the various denomination signing keys returned. + */ + struct GNUNET_HashCode hc; +}; + + +/** + * @brief Information about a denomination key. Denomination keys + * are used to sign coins of a certain value into existence. + */ +struct TALER_DenominationKeyValidityPS +{ + + /** + * Purpose is #TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * The long-term offline master key of the mint that was + * used to create @e signature. + */ + struct TALER_MasterPublicKeyP master; + + /** + * Start time of the validity period for this key. + */ + struct GNUNET_TIME_AbsoluteNBO start; + + /** + * The mint will sign fresh coins between @e start and this time. + * @e expire_withdraw will be somewhat larger than @e start to + * ensure a sufficiently large anonymity set, while also allowing + * the Mint to limit the financial damage in case of a key being + * compromised. Thus, mints with low volume are expected to have a + * longer withdraw period (@e expire_withdraw - @e start) than mints + * with high transaction volume. The period may also differ between + * types of coins. A mint may also have a few denomination keys + * with the same value with overlapping validity periods, to address + * issues such as clock skew. + */ + struct GNUNET_TIME_AbsoluteNBO expire_withdraw; + + /** + * Coins signed with the denomination key must be spent or refreshed + * between @e start and this expiration time. After this time, the + * mint will refuse transactions involving this key as it will + * "drop" the table with double-spending information (shortly after) + * this time. Note that wallets should refresh coins significantly + * before this time to be on the safe side. @e expire_spend must be + * significantly larger than @e expire_withdraw (by months or even + * years). + */ + struct GNUNET_TIME_AbsoluteNBO expire_spend; + + /** + * When do signatures with this denomination key become invalid? + * After this point, these signatures cannot be used in (legal) + * disputes anymore, as the Mint is then allowed to destroy its side + * of the evidence. @e expire_legal is expected to be significantly + * larger than @e expire_spend (by a year or more). + */ + struct GNUNET_TIME_AbsoluteNBO expire_legal; + + /** + * The value of the coins signed with this denomination key. + */ + struct TALER_AmountNBO value; + + /** + * The fee the mint charges when a coin of this type is withdrawn. + * (can be zero). + */ + struct TALER_AmountNBO fee_withdraw; + + /** + * The fee the mint charges when a coin of this type is deposited. + * (can be zero). + */ + struct TALER_AmountNBO fee_deposit; + + /** + * The fee the mint charges when a coin of this type is refreshed. + * (can be zero). + */ + struct TALER_AmountNBO fee_refresh; + + /** + * Hash code of the denomination public key. (Used to avoid having + * the variable-size RSA key in this struct.) + */ + struct GNUNET_HashCode denom_hash; + +}; + + +/** + * @brief Information signed by an auditor affirming + * the master public key and the denomination keys + * of a mint. + */ +struct TALER_MintKeyValidityPS +{ + + /** + * Purpose is #TALER_SIGNATURE_AUDITOR_MINT_KEYS. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * The long-term offline master key of the mint, affirmed by the + * auditor. + */ + struct TALER_MasterPublicKeyP master; + + /** + * Array of hash(es) of the mint's denomination keys. + * Specifically, this is the hash over the + * `struct TALER_DenominationKeyValidityPS`, not just + * the public key (as the auditor needs to check against + * the correct valuations and fee structure). + */ + /* struct GNUNET_HashCode h_dks; */ + +}; + + +GNUNET_NETWORK_STRUCT_END + +#endif diff --git a/src/backend/taler_util.h b/src/backend/taler_util.h new file mode 100644 index 00000000..00397cc8 --- /dev/null +++ b/src/backend/taler_util.h @@ -0,0 +1,162 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/taler_util.h + * @brief Interface for common utility functions + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#ifndef TALER_UTIL_H +#define TALER_UTIL_H + +#include <gnunet/gnunet_util_lib.h> +#include "taler_amount_lib.h" +#include "taler_crypto_lib.h" +#include "taler_json_lib.h" + + + +/* Define logging functions */ +#define TALER_LOG_DEBUG(...) \ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__) + +#define TALER_LOG_WARNING(...) \ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, __VA_ARGS__) + +#define TALER_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; \ + TALER_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 TALER_LOG_GCRY_ERROR(cmd, rc) do { TALER_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; TALER_LOG_ERROR("A Gcrypt call failed at %s:%d with error: %s\n", __FILE__, __LINE__, gcry_strerror(rc)); abort(); } while (0) + + +/** + * Initialize Gcrypt library. + */ +void +TALER_gcrypt_init (void); + + +/** + * Round a time value so that it is suitable for transmission + * via JSON encodings. + * + * @param at time to round + * @return #GNUNET_OK if time was already rounded, #GNUNET_NO if + * it was just now rounded + */ +int +TALER_round_abs_time (struct GNUNET_TIME_Absolute *at); + + +/** + * Round a time value so that it is suitable for transmission + * via JSON encodings. + * + * @param rt time to round + * @return #GNUNET_OK if time was already rounded, #GNUNET_NO if + * it was just now rounded + */ +int +TALER_round_rel_time (struct GNUNET_TIME_Relative *rt); + + +/** + * Load configuration by parsing all configuration + * files in the given directory. + * + * @param base_dir directory with the configuration files + * @return NULL on error, otherwise configuration + */ +struct GNUNET_CONFIGURATION_Handle * +TALER_config_load (const char *base_dir); + + +/** + * Obtain denomination amount from configuration file. + * + * @param section section of the configuration to access + * @param option option of the configuration to access + * @param[out] denom set to the amount found in configuration + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +int +TALER_config_get_denom (struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, + const char *option, + struct TALER_Amount *denom); + + +/** + * Get the path to a specific Taler installation directory or, with + * #GNUNET_OS_IPK_SELF_PREFIX, the current running apps installation + * directory. + * + * @param dirkind what kind of directory is desired? + * @return a pointer to the dir path (to be freed by the caller) + */ +char * +TALER_OS_installation_get_path (enum GNUNET_OS_InstallationPathKind dirkind); + + +/** + * Print out details on command line options (implements --help). + * + * @param ctx command line processing context + * @param scls additional closure (points to about text) + * @param option name of the option + * @param value not used (NULL) + * @return #GNUNET_NO (do not continue, not an error) + */ +int +TALER_GETOPT_format_help_ (struct GNUNET_GETOPT_CommandLineProcessorContext *ctx, + void *scls, + const char *option, + const char *value); + +/** + * Macro defining the option to print the command line + * help text (-h option). + * + * @param about string with brief description of the application + */ +#define TALER_GETOPT_OPTION_HELP(about) \ + { 'h', "help", (const char *) NULL, gettext_noop("print this help"), 0, &TALER_GETOPT_format_help_, (void *) about } + +#endif |