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