diff options
Diffstat (limited to 'src/exchange-tools')
-rw-r--r-- | src/exchange-tools/Makefile.am | 81 | ||||
-rw-r--r-- | src/exchange-tools/taler-auditor-sign.c | 321 | ||||
-rw-r--r-- | src/exchange-tools/taler-exchange-dbinit.c | 105 | ||||
-rw-r--r-- | src/exchange-tools/taler-exchange-keycheck.c | 247 | ||||
-rw-r--r-- | src/exchange-tools/taler-exchange-keyup.c | 1039 | ||||
-rw-r--r-- | src/exchange-tools/taler-exchange-reservemod.c | 206 | ||||
-rw-r--r-- | src/exchange-tools/taler-exchange-sepa.c | 163 |
7 files changed, 2162 insertions, 0 deletions
diff --git a/src/exchange-tools/Makefile.am b/src/exchange-tools/Makefile.am new file mode 100644 index 000000000..4ffabd15d --- /dev/null +++ b/src/exchange-tools/Makefile.am @@ -0,0 +1,81 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +bin_PROGRAMS = \ + taler-auditor-sign \ + taler-exchange-keyup \ + taler-exchange-keycheck \ + taler-exchange-reservemod \ + taler-exchange-sepa \ + taler-exchange-dbinit + +taler_exchange_keyup_SOURCES = \ + taler-exchange-keyup.c +taler_exchange_keyup_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/pq/libtalerpq.la \ + $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ + -lgnunetutil $(XLIB) +taler_exchange_keyup_LDFLAGS = $(POSTGRESQL_LDFLAGS) + +taler_auditor_sign_SOURCES = \ + taler-auditor-sign.c +taler_auditor_sign_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ + -lgnunetutil $(XLIB) + + +taler_exchange_sepa_SOURCES = \ + taler-exchange-sepa.c +taler_exchange_sepa_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil -ljansson $(XLIB) +taler_exchange_sepa_LDFLAGS = $(POSTGRESQL_LDFLAGS) + +taler_exchange_keycheck_SOURCES = \ + taler-exchange-keycheck.c +taler_exchange_keycheck_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ + -lgnunetutil $(XLIB) +taler_exchange_keycheck_LDFLAGS = $(POSTGRESQL_LDFLAGS) + +taler_exchange_reservemod_SOURCES = \ + taler-exchange-reservemod.c +taler_exchange_reservemod_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/pq/libtalerpq.la \ + $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ + -lgnunetutil -ljansson $(XLIB) +taler_exchange_reservemod_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) +taler_exchange_reservemod_CPPFLAGS = \ + -I$(top_srcdir)/src/include \ + -I$(top_srcdir)/src/pq/ \ + $(POSTGRESQL_CPPFLAGS) + +taler_exchange_dbinit_SOURCES = \ + taler-exchange-dbinit.c +taler_exchange_dbinit_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/pq/libtalerpq.la \ + $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ + -lgnunetutil $(XLIB) +taler_exchange_dbinit_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) +taler_exchange_dbinit_CPPFLAGS = \ + -I$(top_srcdir)/src/include \ + -I$(top_srcdir)/src/pq/ \ + $(POSTGRESQL_CPPFLAGS) diff --git a/src/exchange-tools/taler-auditor-sign.c b/src/exchange-tools/taler-auditor-sign.c new file mode 100644 index 000000000..e4821f411 --- /dev/null +++ b/src/exchange-tools/taler-auditor-sign.c @@ -0,0 +1,321 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU 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-auditor-sign.c + * @brief Tool used by the auditor to sign the exchange's master key and the + * denomination key(s). + * @author Christian Grothoff + */ +#include <platform.h> +#include "taler_exchangedb_lib.h" + + +/** + * Are we running in verbose mode? + */ +static int verbose; + +/** + * Filename of the auditor's private key. + */ +static char *auditor_key_file; + +/** + * Exchange's public key (in Crockford base32 encoding). + */ +static char *exchange_public_key; + +/** + * File with the Exchange's denomination keys to sign, itself + * signed by the Exchange's public key. + */ +static char *exchange_request_file; + +/** + * Where should we write the auditor's signature? + */ +static char *output_file; + +/** + * Master public key of the exchange. + */ +static struct TALER_MasterPublicKeyP master_public_key; + + +/** + * Print denomination key details for diagnostics. + * + * @param dk denomination key to print + */ +static void +print_dk (const struct TALER_DenominationKeyValidityPS *dk) +{ + struct TALER_Amount a; + char *s; + + fprintf (stdout, + "Denomination key hash: %s\n", + GNUNET_h2s_full (&dk->denom_hash)); + TALER_amount_ntoh (&a, + &dk->value); + fprintf (stdout, + "Value: %s\n", + s = TALER_amount_to_string (&a)); + GNUNET_free (s); + TALER_amount_ntoh (&a, + &dk->fee_withdraw); + fprintf (stdout, + "Withdraw fee: %s\n", + s = TALER_amount_to_string (&a)); + GNUNET_free (s); + TALER_amount_ntoh (&a, + &dk->fee_deposit); + fprintf (stdout, + "Deposit fee: %s\n", + s = TALER_amount_to_string (&a)); + GNUNET_free (s); + TALER_amount_ntoh (&a, + &dk->fee_refresh); + fprintf (stdout, + "Refresh fee: %s\n", + s = TALER_amount_to_string (&a)); + GNUNET_free (s); + + fprintf (stdout, + "Validity start time: %s\n", + GNUNET_STRINGS_absolute_time_to_string (GNUNET_TIME_absolute_ntoh (dk->start))); + fprintf (stdout, + "Withdraw end time: %s\n", + GNUNET_STRINGS_absolute_time_to_string (GNUNET_TIME_absolute_ntoh (dk->expire_withdraw))); + fprintf (stdout, + "Deposit end time: %s\n", + GNUNET_STRINGS_absolute_time_to_string (GNUNET_TIME_absolute_ntoh (dk->expire_spend))); + fprintf (stdout, + "Legal dispute end time: %s\n", + GNUNET_STRINGS_absolute_time_to_string (GNUNET_TIME_absolute_ntoh (dk->expire_legal))); + + fprintf (stdout, + "\n"); +} + + +/** + * The main function of the taler-auditor-sign tool. This tool is used + * to sign a exchange's master and denomination keys, affirming that the + * auditor is aware of them and will validate the exchange's database with + * respect to these keys. + * + * @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[] = { + {'a', "auditor-key", "FILE", + "file containing the private key of the auditor", 1, + &GNUNET_GETOPT_set_filename, &auditor_key_file}, + TALER_GETOPT_OPTION_HELP ("Private key of the auditor to use for signing"), + {'m', "exchange-key", "KEY", + "public key of the exchange (Crockford base32 encoded)", 1, + &GNUNET_GETOPT_set_filename, &exchange_public_key}, + {'r', "exchange-request", "FILE", + "set of keys the exchange requested the auditor to sign", 1, + &GNUNET_GETOPT_set_string, &exchange_request_file}, + {'o', "output", "FILE", + "where to write our signature", 1, + &GNUNET_GETOPT_set_string, &output_file}, + GNUNET_GETOPT_OPTION_VERSION (VERSION "-" VCS_VERSION), + GNUNET_GETOPT_OPTION_VERBOSE (&verbose), + GNUNET_GETOPT_OPTION_END + }; + struct GNUNET_CRYPTO_EddsaPrivateKey *eddsa_priv; + struct TALER_AuditorSignatureP *sigs; + struct TALER_AuditorPublicKeyP apub; + struct GNUNET_DISK_FileHandle *fh; + struct TALER_DenominationKeyValidityPS *dks; + unsigned int dks_len; + struct TALER_ExchangeKeyValidityPS kv; + off_t in_size; + unsigned int i; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-exchange-keyup", + "WARNING", + NULL)); + if (GNUNET_GETOPT_run ("taler-exchange-keyup", + options, + argc, argv) < 0) + return 1; + if (NULL == auditor_key_file) + { + fprintf (stderr, + "Auditor key file not given\n"); + return 1; + } + eddsa_priv = GNUNET_CRYPTO_eddsa_key_create_from_file (auditor_key_file); + if (NULL == eddsa_priv) + { + fprintf (stderr, + "Failed to initialize auditor key from file `%s'\n", + auditor_key_file); + return 1; + } + GNUNET_CRYPTO_eddsa_key_get_public (eddsa_priv, + &apub.eddsa_pub); + if (NULL == exchange_public_key) + { + fprintf (stderr, + "Exchange public key not given\n"); + GNUNET_free (eddsa_priv); + return 1; + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (exchange_public_key, + strlen (exchange_public_key), + &master_public_key, + sizeof (master_public_key))) + { + fprintf (stderr, + "Public key `%s' malformed\n", + exchange_public_key); + GNUNET_free (eddsa_priv); + return 1; + } + if (NULL == exchange_request_file) + { + fprintf (stderr, + "Exchange signing request not given\n"); + GNUNET_free (eddsa_priv); + return 1; + } + fh = GNUNET_DISK_file_open (exchange_request_file, + GNUNET_DISK_OPEN_READ, + GNUNET_DISK_PERM_NONE); + if (NULL == fh) + { + fprintf (stderr, + "Failed to open file `%s': %s\n", + exchange_request_file, + STRERROR (errno)); + GNUNET_free (eddsa_priv); + return 1; + } + if (GNUNET_OK != + GNUNET_DISK_file_handle_size (fh, + &in_size)) + { + fprintf (stderr, + "Failed to obtain input file size `%s': %s\n", + exchange_request_file, + STRERROR (errno)); + GNUNET_DISK_file_close (fh); + GNUNET_free (eddsa_priv); + return 1; + } + if (0 != (in_size % sizeof (struct TALER_DenominationKeyValidityPS))) + { + fprintf (stderr, + "Input file size of file `%s' is invalid\n", + exchange_request_file); + GNUNET_DISK_file_close (fh); + GNUNET_free (eddsa_priv); + return 1; + } + dks_len = in_size / sizeof (struct TALER_DenominationKeyValidityPS); + kv.purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS); + kv.purpose.size = htonl (sizeof (struct TALER_ExchangeKeyValidityPS)); + kv.master = master_public_key; + dks = GNUNET_new_array (dks_len, + struct TALER_DenominationKeyValidityPS); + sigs = GNUNET_new_array (dks_len, + struct TALER_AuditorSignatureP); + if (in_size != + GNUNET_DISK_file_read (fh, + dks, + in_size)) + { + fprintf (stderr, + "Failed to read input file `%s': %s\n", + exchange_request_file, + STRERROR (errno)); + GNUNET_DISK_file_close (fh); + GNUNET_free (sigs); + GNUNET_free (dks); + GNUNET_free (eddsa_priv); + return 1; + } + GNUNET_DISK_file_close (fh); + for (i=0;i<dks_len;i++) + { + struct TALER_DenominationKeyValidityPS *dk = &dks[i]; + + if (verbose) + print_dk (dk); + kv.start = dk->start; + kv.expire_withdraw = dk->expire_withdraw; + kv.expire_spend = dk->expire_spend; + kv.expire_legal = dk->expire_legal; + kv.value = dk->value; + kv.fee_withdraw = dk->fee_withdraw; + kv.fee_deposit = dk->fee_deposit; + kv.fee_refresh = dk->fee_refresh; + kv.denom_hash = dk->denom_hash; + + /* Finally sign ... */ + GNUNET_CRYPTO_eddsa_sign (eddsa_priv, + &kv.purpose, + &sigs[i].eddsa_sig); + + + } + + if (NULL == output_file) + { + fprintf (stderr, + "Output file not given\n"); + GNUNET_free (dks); + GNUNET_free (sigs); + GNUNET_free (eddsa_priv); + return 1; + } + + /* write result to disk */ + if (GNUNET_OK != + TALER_EXCHANGEDB_auditor_write (output_file, + &apub, + sigs, + &master_public_key, + dks_len, + dks)) + { + fprintf (stderr, + "Failed to write to file `%s': %s\n", + output_file, + STRERROR (errno)); + GNUNET_free (sigs); + GNUNET_free (dks); + return 1; + } + GNUNET_free (sigs); + GNUNET_free (dks); + GNUNET_free (eddsa_priv); + return 0; +} + +/* end of taler-auditor-sign.c */ diff --git a/src/exchange-tools/taler-exchange-dbinit.c b/src/exchange-tools/taler-exchange-dbinit.c new file mode 100644 index 000000000..43a070228 --- /dev/null +++ b/src/exchange-tools/taler-exchange-dbinit.c @@ -0,0 +1,105 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU 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 exchange-tools/taler-exchange-dbinit.c + * @brief Create tables for the exchange database. + * @author Florian Dold + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <libpq-fe.h> +#include "taler_exchangedb_plugin.h" + +/** + * Exchange directory with the keys. + */ +static char *exchange_base_dir; + +/** + * Our configuration. + */ +static struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Our DB plugin. + */ +static struct TALER_EXCHANGEDB_Plugin *plugin; + + +/** + * The main function of the database initialization tool. + * Used to initialize the Taler Exchange's database. + * + * @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[] = { + {'d', "exchange-dir", "DIR", + "exchange directory", 1, + &GNUNET_GETOPT_set_filename, &exchange_base_dir}, + GNUNET_GETOPT_OPTION_HELP ("Initialize Taler Exchange database"), + GNUNET_GETOPT_OPTION_VERSION (VERSION "-" VCS_VERSION), + GNUNET_GETOPT_OPTION_END + }; + + if (GNUNET_GETOPT_run ("taler-exchange-dbinit", + options, + argc, argv) < 0) + return 1; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-exchange-dbinit", + "INFO", + NULL)); + if (NULL == exchange_base_dir) + { + fprintf (stderr, + "Exchange base directory not given.\n"); + return 1; + } + cfg = TALER_config_load (exchange_base_dir); + if (NULL == cfg) + { + fprintf (stderr, + "Failed to load exchange configuration.\n"); + return 1; + } + if (NULL == + (plugin = TALER_EXCHANGEDB_plugin_load (cfg))) + { + fprintf (stderr, + "Failed to initialize database plugin.\n"); + return 1; + } + if (GNUNET_OK != + plugin->create_tables (plugin->cls, + GNUNET_NO)) + { + fprintf (stderr, + "Failed to initialize database.\n"); + TALER_EXCHANGEDB_plugin_unload (plugin); + return 1; + } + TALER_EXCHANGEDB_plugin_unload (plugin); + return 0; +} + +/* end of taler-exchange-dbinit.c */ diff --git a/src/exchange-tools/taler-exchange-keycheck.c b/src/exchange-tools/taler-exchange-keycheck.c new file mode 100644 index 000000000..d6566cd03 --- /dev/null +++ b/src/exchange-tools/taler-exchange-keycheck.c @@ -0,0 +1,247 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU 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-exchange-keycheck.c + * @brief Check exchange keys for validity. Reads the signing and denomination + * keys from the exchange directory and checks to make sure they are + * well-formed. This is purely a diagnostic tool. + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include <platform.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_exchangedb_lib.h" + +/** + * Exchange directory with the keys. + */ +static char *exchange_directory; + +/** + * Our configuration. + */ +static struct GNUNET_CONFIGURATION_Handle *kcfg; + + +/** + * Function called on each signing key. + * + * @param cls closure (NULL) + * @param filename name of the file the key came from + * @param ski the sign key + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +static int +signkeys_iter (void *cls, + const char *filename, + const struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP *ski) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Iterating over key `%s' for start time %s\n", + filename, + GNUNET_STRINGS_absolute_time_to_string + (GNUNET_TIME_absolute_ntoh (ski->issue.start))); + + if (ntohl (ski->issue.purpose.size) != + (sizeof (struct TALER_ExchangeSigningKeyValidityPS) - + offsetof (struct TALER_ExchangeSigningKeyValidityPS, + purpose))) + { + fprintf (stderr, + "Signing key `%s' has invalid purpose size\n", + filename); + return GNUNET_SYSERR; + } + if ( (0 != GNUNET_TIME_absolute_ntoh (ski->issue.start).abs_value_us % 1000000) || + (0 != GNUNET_TIME_absolute_ntoh (ski->issue.expire).abs_value_us % 1000000) || + (0 != GNUNET_TIME_absolute_ntoh (ski->issue.end).abs_value_us % 1000000) ) + { + fprintf (stderr, + "Timestamps are not multiples of a round second\n"); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY, + &ski->issue.purpose, + &ski->issue.signature.eddsa_signature, + &ski->issue.master_public_key.eddsa_pub)) + { + fprintf (stderr, + "Signing key `%s' has invalid signature\n", + filename); + return GNUNET_SYSERR; + } + printf ("Signing key `%s' valid\n", + filename); + return GNUNET_OK; +} + + +/** + * Check signing keys. + * + * @return #GNUNET_OK if the keys are OK + * #GNUNET_NO if not + */ +static int +exchange_signkeys_check () +{ + if (0 > TALER_EXCHANGEDB_signing_keys_iterate (exchange_directory, + &signkeys_iter, + NULL)) + return GNUNET_NO; + return GNUNET_OK; +} + + +/** + * Function called on each denomination key. + * + * @param cls closure (NULL) + * @param dki the denomination key + * @param alias coin alias + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +static int +denomkeys_iter (void *cls, + const char *alias, + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki) +{ + struct GNUNET_HashCode hc; + + if (ntohl (dki->issue.properties.purpose.size) != + sizeof (struct TALER_DenominationKeyValidityPS)) + { + fprintf (stderr, + "Denomination key for `%s' has invalid purpose size\n", + alias); + return GNUNET_SYSERR; + } + + if ( (0 != GNUNET_TIME_absolute_ntoh (dki->issue.properties.start).abs_value_us % 1000000) || + (0 != GNUNET_TIME_absolute_ntoh (dki->issue.properties.expire_withdraw).abs_value_us % 1000000) || + (0 != GNUNET_TIME_absolute_ntoh (dki->issue.properties.expire_legal).abs_value_us % 1000000) || + (0 != GNUNET_TIME_absolute_ntoh (dki->issue.properties.expire_spend).abs_value_us % 1000000) ) + { + fprintf (stderr, + "Timestamps are not multiples of a round second\n"); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY, + &dki->issue.properties.purpose, + &dki->issue.signature.eddsa_signature, + &dki->issue.properties.master.eddsa_pub)) + { + fprintf (stderr, + "Denomination key for `%s' has invalid signature\n", + alias); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_rsa_public_key_hash (dki->denom_pub.rsa_public_key, + &hc); + if (0 != memcmp (&hc, + &dki->issue.properties.denom_hash, + sizeof (struct GNUNET_HashCode))) + { + fprintf (stderr, + "Public key for `%s' does not match signature\n", + alias); + return GNUNET_SYSERR; + } + printf ("Denomination key `%s' is valid\n", + alias); + + return GNUNET_OK; +} + + +/** + * Check denomination keys. + * + * @return #GNUNET_OK if the keys are OK + * #GNUNET_NO if not + */ +static int +exchange_denomkeys_check () +{ + if (0 > TALER_EXCHANGEDB_denomination_keys_iterate (exchange_directory, + &denomkeys_iter, + NULL)) + return GNUNET_NO; + return GNUNET_OK; +} + + +/** + * 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-exchange-keycheck OPTIONS"), + {'d', "directory", "DIRECTORY", + "exchange directory with keys to check", 1, + &GNUNET_GETOPT_set_filename, &exchange_directory}, + GNUNET_GETOPT_OPTION_END + }; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-exchange-keycheck", + "WARNING", + NULL)); + + if (GNUNET_GETOPT_run ("taler-exchange-keycheck", + options, + argc, argv) < 0) + return 1; + if (NULL == exchange_directory) + { + fprintf (stderr, + "Exchange directory not given\n"); + return 1; + } + + kcfg = TALER_config_load (exchange_directory); + if (NULL == kcfg) + { + fprintf (stderr, + "Failed to load exchange configuration\n"); + return 1; + } + if ( (GNUNET_OK != exchange_signkeys_check ()) || + (GNUNET_OK != exchange_denomkeys_check ()) ) + { + GNUNET_CONFIGURATION_destroy (kcfg); + return 1; + } + GNUNET_CONFIGURATION_destroy (kcfg); + return 0; +} + +/* end of taler-exchange-keycheck.c */ diff --git a/src/exchange-tools/taler-exchange-keyup.c b/src/exchange-tools/taler-exchange-keyup.c new file mode 100644 index 000000000..779e3a3d3 --- /dev/null +++ b/src/exchange-tools/taler-exchange-keyup.c @@ -0,0 +1,1039 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU 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-exchange-keyup.c + * @brief Update the exchange's keys for coins and signatures, + * using the exchange's offline master key. + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include <platform.h> +#include "taler_exchangedb_lib.h" + +/** + * When generating filenames from a cryptographic hash, we do not use + * all 512 bits but cut off after this number of characters (in + * base32-encoding). Base32 is 5 bit per character, and given that we + * have very few coin types we hash, at 100 bits the chance of + * collision (by accident over tiny set -- birthday paradox does not + * apply here!) is negligible. + */ +#define HASH_CUTOFF 20 + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Struct with all of the key information for a kind of coin. Hashed + * to generate a unique directory name per coin type. + */ +struct CoinTypeNBOP +{ + /** + * How long are the signatures legally valid? + */ + struct GNUNET_TIME_RelativeNBO duration_legal; + + /** + * How long can the coin be spend? + */ + struct GNUNET_TIME_RelativeNBO duration_spend; + + /** + * How long can the coin be withdrawn (generated)? + */ + struct GNUNET_TIME_RelativeNBO duration_withdraw; + + /** + * What is the value of the coin? + */ + struct TALER_AmountNBO value; + + /** + * What is the fee charged for withdrawl? + */ + struct TALER_AmountNBO fee_withdraw; + + /** + * What is the fee charged for deposits? + */ + struct TALER_AmountNBO fee_deposit; + + /** + * What is the fee charged for melting? + */ + struct TALER_AmountNBO fee_refresh; + + /** + * Key size in NBO. + */ + uint32_t rsa_keysize; +}; + +GNUNET_NETWORK_STRUCT_END + +/** + * Set of all of the parameters that chracterize a coin. + */ +struct CoinTypeParams +{ + + /** + * How long are the signatures legally valid? Should be + * significantly larger than @e duration_spend (i.e. years). + */ + struct GNUNET_TIME_Relative duration_legal; + + + /** + * How long can the coin be spend? Should be significantly + * larger than @e duration_withdraw (i.e. years). + */ + struct GNUNET_TIME_Relative duration_spend; + + /** + * How long can the coin be withdrawn (generated)? Should be small + * enough to limit how many coins will be signed into existence with + * the same key, but large enough to still provide a reasonable + * anonymity set. + */ + struct GNUNET_TIME_Relative duration_withdraw; + + /** + * How much should coin creation (@e duration_withdraw) duration + * overlap with the next coin? Basically, the starting time of two + * coins is always @e duration_withdraw - @e duration_overlap apart. + */ + struct GNUNET_TIME_Relative duration_overlap; + + /** + * What is the value of the coin? + */ + struct TALER_Amount value; + + /** + * What is the fee charged for withdrawl? + */ + struct TALER_Amount fee_withdraw; + + /** + * What is the fee charged for deposits? + */ + struct TALER_Amount fee_deposit; + + /** + * What is the fee charged for melting? + */ + struct TALER_Amount fee_refresh; + + /** + * Time at which this coin is supposed to become valid. + */ + struct GNUNET_TIME_Absolute anchor; + + /** + * Length of the RSA key in bits. + */ + uint32_t rsa_keysize; +}; + + +/** + * Filename of the master private key. + */ +static char *masterkeyfile; + +/** + * Filename where to write denomination key signing + * requests for the auditor (optional, can be NULL). + */ +static char *auditorrequestfile; + +/** + * Handle for writing the output for the auditor. + */ +static FILE *auditor_output_file; + +/** + * Director of the exchange, containing the keys. + */ +static char *exchange_directory; + +/** + * Time to pretend when the key update is executed. + */ +static char *pretend_time_str; + +/** + * Handle to the exchange'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 exchange. + */ +static struct TALER_MasterPrivateKeyP master_priv; + +/** + * Master public key of the exchange. + */ +static struct TALER_MasterPublicKeyP master_public_key; + +/** + * Until what time do we provide keys? + */ +static struct GNUNET_TIME_Absolute lookahead_sign_stamp; + + +/** + * Obtain the name of the directory we use to store signing + * keys created at time @a start. + * + * @param start time at which we create the signing key + * @return name of the directory we should use, basically "$EXCHANGEDIR/$TIME/"; + * (valid until next call to this function) + */ +static const char * +get_signkey_file (struct GNUNET_TIME_Absolute start) +{ + static char dir[4096]; + + GNUNET_snprintf (dir, + sizeof (dir), + "%s" DIR_SEPARATOR_STR TALER_EXCHANGEDB_DIR_SIGNING_KEYS DIR_SEPARATOR_STR "%llu", + exchange_directory, + (unsigned long long) start.abs_value_us); + 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). + * + * @param p coin parameters to convert to a hash + * @param[out] hash set to the hash matching @a p + */ +static void +hash_coin_type (const struct CoinTypeParams *p, + struct GNUNET_HashCode *hash) +{ + struct CoinTypeNBOP p_nbo; + + memset (&p_nbo, + 0, + sizeof (struct CoinTypeNBOP)); + p_nbo.duration_spend = GNUNET_TIME_relative_hton (p->duration_spend); + p_nbo.duration_legal = GNUNET_TIME_relative_hton (p->duration_legal); + p_nbo.duration_withdraw = GNUNET_TIME_relative_hton (p->duration_withdraw); + TALER_amount_hton (&p_nbo.value, + &p->value); + TALER_amount_hton (&p_nbo.fee_withdraw, + &p->fee_withdraw); + TALER_amount_hton (&p_nbo.fee_deposit, + &p->fee_deposit); + TALER_amount_hton (&p_nbo.fee_refresh, + &p->fee_refresh); + p_nbo.rsa_keysize = htonl (p->rsa_keysize); + GNUNET_CRYPTO_hash (&p_nbo, + sizeof (struct CoinTypeNBOP), + hash); +} + + +/** + * Obtain the name of the directory we should use to store coins of + * the given type. The directory name has the format + * "$EXCHANGEDIR/$VALUE/$HASH/" where "$VALUE" represents the value of the + * coin and "$HASH" encodes all of the coin's parameters, generating a + * unique string for each type of coin. Note that the "$HASH" + * includes neither the absolute creation time nor the key of the + * coin, thus the files in the subdirectory really just refer to the + * same type of coins, not the same coin. + * + * @param p coin parameters to convert to a directory name + * @return directory name (valid until next call to this function) + */ +static const char * +get_cointype_dir (const struct CoinTypeParams *p) +{ + static char dir[4096]; + struct GNUNET_HashCode hash; + char *hash_str; + char *val_str; + size_t i; + + hash_coin_type (p, &hash); + hash_str = GNUNET_STRINGS_data_to_string_alloc (&hash, + sizeof (struct GNUNET_HashCode)); + GNUNET_assert (NULL != hash_str); + GNUNET_assert (HASH_CUTOFF <= strlen (hash_str) + 1); + 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] = '_'; + + GNUNET_snprintf (dir, + sizeof (dir), + "%s" DIR_SEPARATOR_STR TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS DIR_SEPARATOR_STR "%s-%s", + exchange_directory, + val_str, + hash_str); + GNUNET_free (hash_str); + GNUNET_free (val_str); + return dir; +} + + +/** + * Obtain the name of the file we would use to store the key + * information for a coin of the given type @a p and validity + * start time @a start + * + * @param p parameters for the coin + * @param start when would the coin begin to be issued + * @return name of the file to use for this coin + * (valid until next call to this function) + */ +static const char * +get_cointype_file (const struct CoinTypeParams *p, + struct GNUNET_TIME_Absolute start) +{ + static char filename[4096]; + const char *dir; + + dir = get_cointype_dir (p); + GNUNET_snprintf (filename, + sizeof (filename), + "%s" DIR_SEPARATOR_STR "%llu", + dir, + (unsigned long long) start.abs_value_us); + return filename; +} + + +/** + * Get the latest key file from a past run of the key generation + * tool. Used to calculate the starting time for the keys we + * generate during this invocation. This function is used to + * handle both signing keys and coin keys, as in both cases + * the filenames correspond to the timestamps we need. + * + * @param cls closure, a `struct GNUNET_TIME_Absolute *`, updated + * to contain the highest timestamp (below #now) + * that was found + * @param filename complete filename (absolute path) + * @return #GNUNET_OK (to continue to iterate) + */ +static int +get_anchor_iter (void *cls, + const char *filename) +{ + struct GNUNET_TIME_Absolute *anchor = cls; + struct GNUNET_TIME_Absolute stamp; + 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; + } + *anchor = GNUNET_TIME_absolute_max (stamp, + *anchor); + return GNUNET_OK; +} + + +/** + * Get the timestamp where the first new key should be generated. + * Relies on correctly named key files (as we do not parse them, + * but just look at the filenames to "guess" at their contents). + * + * @param dir directory that should contain the existing keys + * @param duration how long is one key valid (for signing)? + * @param overlap what's the overlap between the keys validity period? + * @param[out] anchor the timestamp where the first new key should be generated + */ +static 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; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No existing keys found, starting with fresh key set.\n"); + return; + } + *anchor = GNUNET_TIME_UNIT_ZERO_ABS; + if (-1 == + GNUNET_DISK_directory_scan (dir, + &get_anchor_iter, + anchor)) + { + *anchor = now; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No existing keys found, starting with fresh key set.\n"); + return; + } + + if ((GNUNET_TIME_absolute_add (*anchor, + duration)).abs_value_us < now.abs_value_us) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Existing keys are way too old, starting with fresh key set.\n"); + *anchor = now; + } + else if (anchor->abs_value_us != now.abs_value_us) + { + /* Real starting time is the last start time + duration - overlap */ + *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 */ +} + + +/** + * Create a exchange signing key (for signing exchange messages, not for coins) + * and assert its correctness by signing it with the master key. + * + * @param start start time of the validity period for the key + * @param duration how long should the key be valid + * @param end when do all signatures by this key expire + * @param[out] pi set to the signing key information + */ +static void +create_signkey_issue_priv (struct GNUNET_TIME_Absolute start, + struct GNUNET_TIME_Relative duration, + struct GNUNET_TIME_Absolute end, + struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP *pi) +{ + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + struct TALER_ExchangeSigningKeyValidityPS *issue = &pi->issue; + + priv = GNUNET_CRYPTO_eddsa_key_create (); + pi->signkey_priv.eddsa_priv = *priv; + GNUNET_free (priv); + issue->master_public_key = master_public_key; + issue->start = GNUNET_TIME_absolute_hton (start); + issue->expire = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (start, + duration)); + issue->end = GNUNET_TIME_absolute_hton (end); + GNUNET_CRYPTO_eddsa_key_get_public (&pi->signkey_priv.eddsa_priv, + &issue->signkey_pub.eddsa_pub); + issue->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY); + issue->purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS) - + offsetof (struct TALER_ExchangeSigningKeyValidityPS, + purpose)); + + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&master_priv.eddsa_priv, + &issue->purpose, + &issue->signature.eddsa_signature)); +} + + +/** + * Generate signing keys starting from the last key found to + * the lookahead time. + * + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +exchange_keys_update_signkeys () +{ + struct GNUNET_TIME_Relative signkey_duration; + struct GNUNET_TIME_Relative legal_duration; + struct GNUNET_TIME_Absolute anchor; + char *signkey_dir; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + "exchange_keys", + "signkey_duration", + &signkey_duration)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange_keys", + "signkey_duration"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + "exchange_keys", + "legal_duration", + &legal_duration)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "exchange_keys", + "legal_duration", + "fails to specify valid timeframe"); + return GNUNET_SYSERR; + } + if (signkey_duration.rel_value_us > legal_duration.rel_value_us) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "exchange_keys", + "legal_duration", + "must be longer than signkey_duration"); + return GNUNET_SYSERR; + } + TALER_round_rel_time (&signkey_duration); + GNUNET_asprintf (&signkey_dir, + "%s" DIR_SEPARATOR_STR TALER_EXCHANGEDB_DIR_SIGNING_KEYS, + exchange_directory); + /* make sure the directory exists */ + if (GNUNET_OK != + GNUNET_DISK_directory_create (signkey_dir)) + { + fprintf (stderr, + "Failed to create signing key directory\n"); + return GNUNET_SYSERR; + } + + get_anchor (signkey_dir, + signkey_duration, + GNUNET_TIME_UNIT_ZERO /* no overlap for signing keys */, + &anchor); + + while (anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) + { + const char *skf; + struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP signkey_issue; + ssize_t nwrite; + struct GNUNET_TIME_Absolute end; + + skf = get_signkey_file (anchor); + end = GNUNET_TIME_absolute_add (anchor, + legal_duration); + GNUNET_break (GNUNET_YES != + GNUNET_DISK_file_test (skf)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Generating signing key for %s.\n", + GNUNET_STRINGS_absolute_time_to_string (anchor)); + create_signkey_issue_priv (anchor, + signkey_duration, + end, + &signkey_issue); + nwrite = GNUNET_DISK_fn_write (skf, + &signkey_issue, + sizeof (struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP), + GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_USER_READ); + if (sizeof (struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP) != nwrite) + { + fprintf (stderr, + "Failed to write to file `%s': %s\n", + skf, + STRERROR (errno)); + return GNUNET_SYSERR; + } + anchor = GNUNET_TIME_absolute_add (anchor, + signkey_duration); + } + return GNUNET_OK; +} + + +/** + * Parse configuration for coin type parameters. Also determines + * our anchor by looking at the existing coins of the same type. + * + * @param ct section in the configuration file giving the coin type parameters + * @param[out] params set to the coin parameters from the configuration + * @return #GNUNET_OK on success, #GNUNET_SYSERR if the configuration is invalid + */ +static int +get_cointype_params (const char *ct, + struct CoinTypeParams *params) +{ + const char *dir; + unsigned long long rsa_keysize; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + ct, + "duration_withdraw", + ¶ms->duration_withdraw)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "duration_withdraw"); + return GNUNET_SYSERR; + } + TALER_round_rel_time (¶ms->duration_withdraw); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + ct, + "duration_spend", + ¶ms->duration_spend)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "duration_spend"); + return GNUNET_SYSERR; + } + TALER_round_rel_time (¶ms->duration_spend); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + ct, + "duration_legal", + ¶ms->duration_legal)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "duration_legal"); + return GNUNET_SYSERR; + } + TALER_round_rel_time (¶ms->duration_legal); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + ct, + "duration_overlap", + ¶ms->duration_overlap)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "exchange_denom_duration_overlap"); + return GNUNET_SYSERR; + } + TALER_round_rel_time (¶ms->duration_overlap); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (kcfg, + ct, + "rsa_keysize", + &rsa_keysize)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "rsa_keysize"); + return GNUNET_SYSERR; + } + if ( (rsa_keysize > 4 * 2048) || + (rsa_keysize < 1024) ) + { + fprintf (stderr, + "Given RSA keysize %llu outside of permitted range\n", + rsa_keysize); + return GNUNET_SYSERR; + } + params->rsa_keysize = (unsigned int) rsa_keysize; + if (GNUNET_OK != + TALER_config_get_denom (kcfg, + ct, + "value", + ¶ms->value)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "value"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_config_get_denom (kcfg, + ct, + "fee_withdraw", + ¶ms->fee_withdraw)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "fee_withdraw"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_config_get_denom (kcfg, + ct, + "fee_deposit", + ¶ms->fee_deposit)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "fee_deposit"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_config_get_denom (kcfg, + ct, + "fee_refresh", + ¶ms->fee_refresh)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "fee_refresh"); + return GNUNET_SYSERR; + } + + dir = get_cointype_dir (params); + get_anchor (dir, + params->duration_withdraw, + params->duration_overlap, + ¶ms->anchor); + return GNUNET_OK; +} + + +/** + * Initialize the private and public key information structure for + * signing coins into existence. Generates the private signing key + * and signes it together with the coin's meta data using the master + * signing key. + * + * @param params parameters used to initialize the @a dki + * @param[out] dki initialized according to @a params + */ +static void +create_denomkey_issue (const struct CoinTypeParams *params, + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki) +{ + dki->denom_priv.rsa_private_key + = GNUNET_CRYPTO_rsa_private_key_create (params->rsa_keysize); + GNUNET_assert (NULL != dki->denom_priv.rsa_private_key); + dki->denom_pub.rsa_public_key + = GNUNET_CRYPTO_rsa_private_key_get_public (dki->denom_priv.rsa_private_key); + GNUNET_CRYPTO_rsa_public_key_hash (dki->denom_pub.rsa_public_key, + &dki->issue.properties.denom_hash); + dki->issue.properties.master = master_public_key; + dki->issue.properties.start = GNUNET_TIME_absolute_hton (params->anchor); + dki->issue.properties.expire_withdraw = + GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, + params->duration_withdraw)); + dki->issue.properties.expire_spend = + GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, + params->duration_spend)); + dki->issue.properties.expire_legal = + GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, + params->duration_legal)); + TALER_amount_hton (&dki->issue.properties.value, + ¶ms->value); + TALER_amount_hton (&dki->issue.properties.fee_withdraw, + ¶ms->fee_withdraw); + TALER_amount_hton (&dki->issue.properties.fee_deposit, + ¶ms->fee_deposit); + TALER_amount_hton (&dki->issue.properties.fee_refresh, + ¶ms->fee_refresh); + dki->issue.properties.purpose.purpose + = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY); + dki->issue.properties.purpose.size + = htonl (sizeof (struct TALER_DenominationKeyValidityPS)); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&master_priv.eddsa_priv, + &dki->issue.properties.purpose, + &dki->issue.signature.eddsa_signature)); +} + + +/** + * Generate new coin signing keys for the coin type of the given @a + * coin_alias. + * + * @param cls a `int *`, to be set to #GNUNET_SYSERR on failure + * @param coin_alias name of the coin's section in the configuration + */ +static void +exchange_keys_update_cointype (void *cls, + const char *coin_alias) +{ + int *ret = cls; + struct CoinTypeParams p; + const char *dkf; + struct TALER_EXCHANGEDB_DenominationKeyIssueInformation denomkey_issue; + + if (0 != strncasecmp (coin_alias, + "coin_", + strlen ("coin_"))) + return; /* not a coin definition */ + if (GNUNET_OK != + get_cointype_params (coin_alias, + &p)) + { + *ret = GNUNET_SYSERR; + return; + } + if (GNUNET_OK != + GNUNET_DISK_directory_create (get_cointype_dir (&p))) + { + *ret = GNUNET_SYSERR; + return; + } + + while (p.anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) + { + dkf = get_cointype_file (&p, + p.anchor); + GNUNET_break (GNUNET_YES != GNUNET_DISK_file_test (dkf)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Generating denomination key for type `%s', start %s at %s\n", + coin_alias, + GNUNET_STRINGS_absolute_time_to_string (p.anchor), + dkf); + create_denomkey_issue (&p, + &denomkey_issue); + if (GNUNET_OK != + TALER_EXCHANGEDB_denomination_key_write (dkf, + &denomkey_issue)) + { + fprintf (stderr, + "Failed to write denomination key information to file `%s'.\n", + dkf); + *ret = GNUNET_SYSERR; + GNUNET_CRYPTO_rsa_private_key_free (denomkey_issue.denom_priv.rsa_private_key); + return; + } + if ( (NULL != auditor_output_file) && + (sizeof (denomkey_issue.issue.properties) != + fwrite (&denomkey_issue.issue.properties, + sizeof (struct TALER_DenominationKeyValidityPS), + 1, + auditor_output_file)) ) + { + fprintf (stderr, + "Failed to write denomination key information to %s: %s\n", + auditorrequestfile, + STRERROR (errno)); + *ret = GNUNET_SYSERR; + return; + } + GNUNET_CRYPTO_rsa_private_key_free (denomkey_issue.denom_priv.rsa_private_key); + p.anchor = GNUNET_TIME_absolute_add (p.anchor, + p.duration_spend); + p.anchor = GNUNET_TIME_absolute_subtract (p.anchor, + p.duration_overlap); + } +} + + +/** + * Update all of the denomination keys of the exchange. + * + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +exchange_keys_update_denomkeys () +{ + int ok; + + ok = GNUNET_OK; + GNUNET_CONFIGURATION_iterate_sections (kcfg, + &exchange_keys_update_cointype, + &ok); + return ok; +} + + +/** + * The main function of the taler-exchange-keyup tool. This tool is used + * to create the signing and denomination keys for the exchange. It uses + * the long-term offline private key and writes the (additional) key + * files to the respective exchange directory (from where they can then be + * copied to the online server). Note that we need (at least) the + * most recent generated previous keys so as to align the validity + * periods. + * + * @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[] = { + {'d', "exchange-dir", "DIR", + "exchange directory with keys to update", 1, + &GNUNET_GETOPT_set_filename, &exchange_directory}, + TALER_GETOPT_OPTION_HELP ("Setup signing and denomination keys for a Taler exchange"), + {'m', "master-key", "FILE", + "master key file (private key)", 1, + &GNUNET_GETOPT_set_filename, &masterkeyfile}, + {'o', "output", "FILE", + "auditor denomination key signing request file to create", 1, + &GNUNET_GETOPT_set_filename, &auditorrequestfile}, + {'t', "time", "TIMESTAMP", + "pretend it is a different time for the update", 0, + &GNUNET_GETOPT_set_string, &pretend_time_str}, + GNUNET_GETOPT_OPTION_VERSION (VERSION "-" VCS_VERSION), + GNUNET_GETOPT_OPTION_END + }; + struct GNUNET_TIME_Relative lookahead_sign; + struct GNUNET_CRYPTO_EddsaPrivateKey *eddsa_priv; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-exchange-keyup", + "WARNING", + NULL)); + + if (GNUNET_GETOPT_run ("taler-exchange-keyup", + options, + argc, argv) < 0) + return 1; + if (NULL == exchange_directory) + { + fprintf (stderr, + "Exchange 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 `%s' invalid\n", + pretend_time_str); + return 1; + } + } + else + { + now = GNUNET_TIME_absolute_get (); + } + TALER_round_abs_time (&now); + + kcfg = TALER_config_load (exchange_directory); + if (NULL == kcfg) + { + fprintf (stderr, + "Failed to load exchange configuration\n"); + return 1; + } + if (NULL == masterkeyfile) + { + fprintf (stderr, + "Master key file not given\n"); + return 1; + } + eddsa_priv = GNUNET_CRYPTO_eddsa_key_create_from_file (masterkeyfile); + if (NULL == eddsa_priv) + { + fprintf (stderr, + "Failed to initialize master key from file `%s'\n", + masterkeyfile); + return 1; + } + master_priv.eddsa_priv = *eddsa_priv; + GNUNET_free (eddsa_priv); + GNUNET_CRYPTO_eddsa_key_get_public (&master_priv.eddsa_priv, + &master_public_key.eddsa_pub); + + if (NULL != auditorrequestfile) + { + auditor_output_file = FOPEN (auditorrequestfile, + "w"); + if (NULL == auditor_output_file) + { + fprintf (stderr, + "Failed to open `%s' for writing: %s\n", + auditorrequestfile, + STRERROR (errno)); + return 1; + } + } + + /* check if key from file matches the one from the configuration */ + { + struct GNUNET_CRYPTO_EddsaPublicKey master_public_key_from_cfg; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_data (kcfg, + "exchange", + "master_public_key", + &master_public_key_from_cfg, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "master_public_key"); + return 1; + } + if (0 != + memcmp (&master_public_key, + &master_public_key_from_cfg, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "master_public_key", + _("does not match with private key")); + return 1; + } + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + "exchange_keys", + "lookahead_sign", + &lookahead_sign)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange_keys", + "lookahead_sign"); + return GNUNET_SYSERR; + } + if (0 == lookahead_sign.rel_value_us) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "exchange_keys", + "lookahead_sign", + _("must not be zero")); + return GNUNET_SYSERR; + } + TALER_round_rel_time (&lookahead_sign); + lookahead_sign_stamp = GNUNET_TIME_absolute_add (now, + lookahead_sign); + + + /* finally, do actual work */ + if (GNUNET_OK != exchange_keys_update_signkeys ()) + return 1; + + if (GNUNET_OK != exchange_keys_update_denomkeys ()) + return 1; + if (NULL != auditor_output_file) + { + FCLOSE (auditor_output_file); + auditor_output_file = NULL; + } + return 0; +} + +/* end of taler-exchange-keyup.c */ diff --git a/src/exchange-tools/taler-exchange-reservemod.c b/src/exchange-tools/taler-exchange-reservemod.c new file mode 100644 index 000000000..984b7f8ee --- /dev/null +++ b/src/exchange-tools/taler-exchange-reservemod.c @@ -0,0 +1,206 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU 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-exchange-reservemod.c + * @brief Modify reserves. Allows manipulation of reserve balances. + * @author Florian Dold + * @author Benedikt Mueller + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <libpq-fe.h> +#include <jansson.h> +#include "taler_exchangedb_plugin.h" + +/** + * Director of the exchange, containing the keys. + */ +static char *exchange_directory; + +/** + * Handle to the exchange's configuration + */ +static struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Our DB plugin. + */ +static struct TALER_EXCHANGEDB_Plugin *plugin; + + +/** + * 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) +{ + char *reserve_pub_str = NULL; + char *add_str = NULL; + struct TALER_Amount add_value; + char *details = NULL; + json_t *jdetails; + json_error_t error; + struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_EXCHANGEDB_Session *session; + const struct GNUNET_GETOPT_CommandLineOption options[] = { + {'a', "add", "DENOM", + "value to add", 1, + &GNUNET_GETOPT_set_string, &add_str}, + {'d', "exchange-dir", "DIR", + "exchange directory with keys to update", 1, + &GNUNET_GETOPT_set_filename, &exchange_directory}, + {'D', "details", "JSON", + "details about the bank transaction which justify why we add this amount", 1, + &GNUNET_GETOPT_set_string, &details}, + TALER_GETOPT_OPTION_HELP ("Deposit funds into a Taler reserve"), + {'R', "reserve", "KEY", + "reserve (public key) to modify", 1, + &GNUNET_GETOPT_set_string, &reserve_pub_str}, + GNUNET_GETOPT_OPTION_VERSION (VERSION "-" VCS_VERSION), + GNUNET_GETOPT_OPTION_END + }; + int ret; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-exchange-reservemod", + "WARNING", + NULL)); + + if (GNUNET_GETOPT_run ("taler-exchange-reservemod", + options, + argc, argv) < 0) + return 1; + if (NULL == exchange_directory) + { + fprintf (stderr, + "Exchange directory not given\n"); + GNUNET_free_non_null (add_str); + GNUNET_free_non_null (details); + GNUNET_free_non_null (reserve_pub_str); + return 1; + } + if ((NULL == reserve_pub_str) || + (GNUNET_OK != + GNUNET_STRINGS_string_to_data (reserve_pub_str, + strlen (reserve_pub_str), + &reserve_pub, + sizeof (struct TALER_ReservePublicKeyP)))) + { + fprintf (stderr, + "Parsing reserve key invalid\n"); + GNUNET_free_non_null (add_str); + GNUNET_free_non_null (details); + GNUNET_free_non_null (reserve_pub_str); + return 1; + } + if ( (NULL == add_str) || + (GNUNET_OK != + TALER_string_to_amount (add_str, + &add_value)) ) + { + fprintf (stderr, + "Failed to parse currency amount `%s'\n", + add_str); + GNUNET_free_non_null (add_str); + GNUNET_free_non_null (details); + GNUNET_free_non_null (reserve_pub_str); + return 1; + } + + if (NULL == details) + { + fprintf (stderr, + "No wiring details given (justification required)\n"); + GNUNET_free_non_null (add_str); + GNUNET_free_non_null (reserve_pub_str); + return 1; + } + + cfg = TALER_config_load (exchange_directory); + if (NULL == cfg) + { + fprintf (stderr, + "Failed to load exchange configuration\n"); + GNUNET_free_non_null (add_str); + GNUNET_free_non_null (details); + GNUNET_free_non_null (reserve_pub_str); + return 1; + } + ret = 1; + if (NULL == + (plugin = TALER_EXCHANGEDB_plugin_load (cfg))) + { + fprintf (stderr, + "Failed to initialize database plugin.\n"); + goto cleanup; + } + + session = plugin->get_session (plugin->cls, + GNUNET_NO); + if (NULL == session) + { + fprintf (stderr, + "Failed to initialize DB session\n"); + goto cleanup; + } + jdetails = json_loads (details, + JSON_REJECT_DUPLICATES, + &error); + if (NULL == jdetails) + { + fprintf (stderr, + "Failed to parse JSON transaction details `%s': %s (%s)\n", + details, + error.text, + error.source); + goto cleanup; + } + /* FIXME: maybe allow passing timestamp via command-line? */ + ret = plugin->reserves_in_insert (plugin->cls, + session, + &reserve_pub, + &add_value, + GNUNET_TIME_absolute_get (), + jdetails); + json_decref (jdetails); + if (GNUNET_SYSERR == ret) + { + fprintf (stderr, + "Failed to update reserve.\n"); + goto cleanup; + } + if (GNUNET_NO == ret) + { + fprintf (stderr, + "Record exists, reserve not updated.\n"); + } + ret = 0; + cleanup: + if (NULL != plugin) + TALER_EXCHANGEDB_plugin_unload (plugin); + if (NULL != cfg) + GNUNET_CONFIGURATION_destroy (cfg); + GNUNET_free_non_null (add_str); + GNUNET_free_non_null (details); + GNUNET_free_non_null (reserve_pub_str); + return ret; +} + +/* end taler-exchange-reservemod.c */ diff --git a/src/exchange-tools/taler-exchange-sepa.c b/src/exchange-tools/taler-exchange-sepa.c new file mode 100644 index 000000000..a3ac95436 --- /dev/null +++ b/src/exchange-tools/taler-exchange-sepa.c @@ -0,0 +1,163 @@ +/* + This file is part of TALER + Copyright (C) 2015 GNUnet e.V. + + 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-exchange-sepa.c + * @brief Create signed response for /wire/sepa requests. + * @author Christian Grothoff + */ +#include <platform.h> +#include <jansson.h> +#include "taler_crypto_lib.h" +#include "taler_signatures.h" + + +/** + * Filename of the master private key. + */ +static char *masterkeyfile; + +/** + * Account holder name. + */ +static char *sepa_name; + +/** + * IBAN number. + */ +static char *iban; + +/** + * BIC number. + */ +static char *bic; + +/** + * Where to write the result. + */ +static char *output_filename; + + +/** + * The main function of the taler-exchange-sepa tool. This tool is used + * to sign the SEPA bank account details using the master key. + * + * @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[] = { + {'b', "bic", "BICCODE", + "bank BIC code", 1, + &GNUNET_GETOPT_set_string, &bic}, + {'i', "iban", "IBAN", + "IBAN number of the account", 1, + &GNUNET_GETOPT_set_string, &iban}, + {'m', "master-key", "FILE", + "master key file (private key)", 1, + &GNUNET_GETOPT_set_filename, &masterkeyfile}, + {'n', "name", "NAME", + "name of the account holder", 1, + &GNUNET_GETOPT_set_string, &sepa_name}, + {'o', "output", "FILE", + "where to write the result", 1, + &GNUNET_GETOPT_set_filename, &output_filename}, + TALER_GETOPT_OPTION_HELP ("Setup /wire/sepa response"), + GNUNET_GETOPT_OPTION_VERSION (VERSION "-" VCS_VERSION), + GNUNET_GETOPT_OPTION_END + }; + struct GNUNET_CRYPTO_EddsaPrivateKey *eddsa_priv; + struct TALER_MasterWireSepaDetailsPS wsd; + struct TALER_MasterSignatureP sig; + struct GNUNET_HashContext *hc; + json_t *reply; + char *json_str; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-exchange-sepa", + "WARNING", + NULL)); + + if (GNUNET_GETOPT_run ("taler-exchange-sepa", + options, + argc, argv) < 0) + return 1; + if (NULL == masterkeyfile) + { + fprintf (stderr, + "Master key file not given\n"); + return 1; + } + eddsa_priv = GNUNET_CRYPTO_eddsa_key_create_from_file (masterkeyfile); + if (NULL == eddsa_priv) + { + fprintf (stderr, + "Failed to initialize master key from file `%s'\n", + masterkeyfile); + return 1; + } + + /* Compute message to sign */ + hc = GNUNET_CRYPTO_hash_context_start (); + GNUNET_CRYPTO_hash_context_read (hc, + sepa_name, + strlen (sepa_name) + 1); + GNUNET_CRYPTO_hash_context_read (hc, + iban, + strlen (iban) + 1); + GNUNET_CRYPTO_hash_context_read (hc, + bic, + strlen (bic) + 1); + wsd.purpose.size = htonl (sizeof (wsd)); + wsd.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SEPA_DETAILS); + GNUNET_CRYPTO_hash_context_finish (hc, + &wsd.h_sepa_details); + GNUNET_CRYPTO_eddsa_sign (eddsa_priv, + &wsd.purpose, + &sig.eddsa_signature); + GNUNET_free (eddsa_priv); + + /* build JSON message */ + reply = json_pack ("{s:s, s:s, s:s, s:o}", + "receiver_name", sepa_name, + "iban", iban, + "bic", bic, + "sig", TALER_json_from_data (&sig, + sizeof (sig))); + GNUNET_assert (NULL != reply); + + /* dump result to stdout */ + json_str = json_dumps (reply, JSON_INDENT(2)); + GNUNET_assert (NULL != json_str); + + if (NULL != output_filename) + { + fclose (stdout); + stdout = fopen (output_filename, + "w+"); + } + fprintf (stdout, + "%s", + json_str); + fflush (stdout); + free (json_str); + return 0; +} + +/* end of taler-exchange-sepa.c */ |