From 57d1f08dbca256f5fe16d57b29bfa523dec8f6c4 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Thu, 8 Jan 2015 18:37:20 +0100 Subject: -initial import for mint --- src/mint/taler-mint-keyup.c | 657 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 657 insertions(+) create mode 100644 src/mint/taler-mint-keyup.c (limited to 'src/mint/taler-mint-keyup.c') diff --git a/src/mint/taler-mint-keyup.c b/src/mint/taler-mint-keyup.c new file mode 100644 index 000000000..8a1a77882 --- /dev/null +++ b/src/mint/taler-mint-keyup.c @@ -0,0 +1,657 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see +*/ + +/** + * @file taler-mint-keyup.c + * @brief Update the mint's keys for coins and signatures, + * using the mint's offline master key. + * @author Florian Dold + * @author Benedikt Mueller + */ + +#include +#include +#include "taler_util.h" +#include "taler_signatures.h" +#include "mint.h" + +#define HASH_CUTOFF 20 + +/** + * Macro to round microseconds to seconds in GNUNET_TIME_* structs. + */ +#define ROUND_TO_SECS(name,us_field) name.us_field -= name.us_field % (1000 * 1000); + + +GNUNET_NETWORK_STRUCT_BEGIN + +struct CoinTypeNBO +{ + struct GNUNET_TIME_RelativeNBO duration_spend; + struct GNUNET_TIME_RelativeNBO duration_withdraw; + struct TALER_AmountNBO value; + struct TALER_AmountNBO fee_withdraw; + struct TALER_AmountNBO fee_deposit; + struct TALER_AmountNBO fee_refresh; +}; + +GNUNET_NETWORK_STRUCT_END + +struct CoinTypeParams +{ + struct GNUNET_TIME_Relative duration_spend; + struct GNUNET_TIME_Relative duration_withdraw; + struct GNUNET_TIME_Relative duration_overlap; + struct TALER_Amount value; + struct TALER_Amount fee_withdraw; + struct TALER_Amount fee_deposit; + struct TALER_Amount fee_refresh; + struct GNUNET_TIME_Absolute anchor; +}; + + +/** + * Filename of the master private key. + */ +static char *masterkeyfile; + +/** + * Director of the mint, containing the keys. + */ +static char *mintdir; + +/** + * Time to pretend when the key update is executed. + */ +static char *pretend_time_str; + +/** + * Handle to the mint's configuration + */ +static struct GNUNET_CONFIGURATION_Handle *kcfg; + +/** + * Time when the key update is executed. Either the actual current time, or a + * pretended time. + */ +static struct GNUNET_TIME_Absolute now; + +/** + * Master private key of the mint. + */ +static struct GNUNET_CRYPTO_EddsaPrivateKey *master_priv; + +/** + * Master public key of the mint. + */ +static struct GNUNET_CRYPTO_EddsaPublicKey *master_pub; + +/** + * Until what time do we provide keys? + */ +static struct GNUNET_TIME_Absolute lookahead_sign_stamp; + + +int +config_get_denom (const char *section, const char *option, struct TALER_Amount *denom) +{ + char *str; + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg, section, option, &str)) + return GNUNET_NO; + if (GNUNET_OK != TALER_string_to_amount (str, denom)) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +char * +get_signkey_dir () +{ + char *dir; + size_t len; + len = GNUNET_asprintf (&dir, ("%s" DIR_SEPARATOR_STR DIR_SIGNKEYS), mintdir); + GNUNET_assert (len > 0); + return dir; +} + + +char * +get_signkey_file (struct GNUNET_TIME_Absolute start) +{ + char *dir; + size_t len; + len = GNUNET_asprintf (&dir, ("%s" DIR_SEPARATOR_STR DIR_SIGNKEYS DIR_SEPARATOR_STR "%llu"), + mintdir, (long long) start.abs_value_us); + GNUNET_assert (len > 0); + return dir; +} + + + +/** + * Hash the data defining the coin type. + * Exclude information that may not be the same for all + * instances of the coin type (i.e. the anchor, overlap). + */ +void +hash_coin_type (const struct CoinTypeParams *p, struct GNUNET_HashCode *hash) +{ + struct CoinTypeNBO p_nbo; + + memset (&p_nbo, 0, sizeof (struct CoinTypeNBO)); + + p_nbo.duration_spend = GNUNET_TIME_relative_hton (p->duration_spend); + p_nbo.duration_withdraw = GNUNET_TIME_relative_hton (p->duration_withdraw); + p_nbo.value = TALER_amount_hton (p->value); + p_nbo.fee_withdraw = TALER_amount_hton (p->fee_withdraw); + p_nbo.fee_deposit = TALER_amount_hton (p->fee_deposit); + p_nbo.fee_refresh = TALER_amount_hton (p->fee_refresh); + + GNUNET_CRYPTO_hash (&p_nbo, sizeof (struct CoinTypeNBO), hash); +} + + +static const char * +get_cointype_dir (const struct CoinTypeParams *p) +{ + static char dir[4096]; + size_t len; + struct GNUNET_HashCode hash; + char *hash_str; + char *val_str; + unsigned int i; + + hash_coin_type (p, &hash); + hash_str = TALER_data_to_string_alloc (&hash, sizeof (struct GNUNET_HashCode)); + GNUNET_assert (HASH_CUTOFF <= strlen (hash_str) + 1); + GNUNET_assert (NULL != hash_str); + hash_str[HASH_CUTOFF] = 0; + + val_str = TALER_amount_to_string (p->value); + for (i = 0; i < strlen (val_str); i++) + if (':' == val_str[i] || '.' == val_str[i]) + val_str[i] = '_'; + + len = GNUNET_snprintf (dir, sizeof (dir), + ("%s" DIR_SEPARATOR_STR DIR_DENOMKEYS DIR_SEPARATOR_STR "%s-%s"), + mintdir, val_str, hash_str); + GNUNET_assert (len > 0); + GNUNET_free (hash_str); + return dir; +} + + +static const char * +get_cointype_file (struct CoinTypeParams *p, + struct GNUNET_TIME_Absolute start) +{ + const char *dir; + static char filename[4096]; + size_t len; + dir = get_cointype_dir (p); + len = GNUNET_snprintf (filename, sizeof (filename), ("%s" DIR_SEPARATOR_STR "%llu"), + dir, (unsigned long long) start.abs_value_us); + GNUNET_assert (len > 0); + return filename; +} + + +/** + * Get the latest key file from the past. + * + * @param cls closure + * @param filename complete filename (absolute path) + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +static int +get_anchor_iter (void *cls, + const char *filename) +{ + struct GNUNET_TIME_Absolute stamp; + struct GNUNET_TIME_Absolute *anchor = cls; + const char *base; + char *end = NULL; + + base = GNUNET_STRINGS_get_short_name (filename); + stamp.abs_value_us = strtol (base, &end, 10); + + if ((NULL == end) || (0 != *end)) + { + fprintf(stderr, "Ignoring unexpected file '%s'.\n", filename); + return GNUNET_OK; + } + + // TODO: check if it's actually a valid key file + + if ((stamp.abs_value_us <= now.abs_value_us) && (stamp.abs_value_us > anchor->abs_value_us)) + *anchor = stamp; + + return GNUNET_OK; +} + + +/** + * Get the timestamp where the first new key should be generated. + * Relies on correctly named key files. + * + * @param dir directory with the signed stuff + * @param duration how long is one key valid? + * @param overlap what's the overlap between the keys validity period? + * @param[out] anchor the timestamp where the first new key should be generated + */ +void +get_anchor (const char *dir, + struct GNUNET_TIME_Relative duration, + struct GNUNET_TIME_Relative overlap, + struct GNUNET_TIME_Absolute *anchor) +{ + GNUNET_assert (0 == duration.rel_value_us % 1000000); + GNUNET_assert (0 == overlap.rel_value_us % 1000000); + if (GNUNET_YES != GNUNET_DISK_directory_test (dir, GNUNET_YES)) + { + *anchor = now; + printf ("Can't look for anchor (%s)\n", dir); + return; + } + + *anchor = GNUNET_TIME_UNIT_ZERO_ABS; + if (-1 == GNUNET_DISK_directory_scan (dir, &get_anchor_iter, anchor)) + { + *anchor = now; + return; + } + + if ((GNUNET_TIME_absolute_add (*anchor, duration)).abs_value_us < now.abs_value_us) + { + // there's no good anchor, start from now + // (existing keys are too old) + *anchor = now; + } + else if (anchor->abs_value_us != now.abs_value_us) + { + // we have a good anchor + *anchor = GNUNET_TIME_absolute_add (*anchor, duration); + *anchor = GNUNET_TIME_absolute_subtract (*anchor, overlap); + } + // anchor is now the stamp where we need to create a new key +} + +static void +create_signkey_issue (struct GNUNET_TIME_Absolute start, + struct GNUNET_TIME_Relative duration, + struct TALER_MINT_SignKeyIssue *issue) +{ + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + + priv = GNUNET_CRYPTO_eddsa_key_create (); + GNUNET_assert (NULL != priv); + issue->signkey_priv = *priv; + GNUNET_free (priv); + issue->master_pub = *master_pub; + issue->start = GNUNET_TIME_absolute_hton (start); + issue->expire = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (start, duration)); + + GNUNET_CRYPTO_eddsa_key_get_public (&issue->signkey_priv, &issue->signkey_pub); + + issue->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNKEY); + issue->purpose.size = htonl (sizeof (struct TALER_MINT_SignKeyIssue) - offsetof (struct TALER_MINT_SignKeyIssue, purpose)); + + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_sign (master_priv, &issue->purpose, &issue->signature)) + { + GNUNET_abort (); + } +} + + +static int +check_signkey_valid (const char *signkey_filename) +{ + // FIXME: do real checks + return GNUNET_OK; +} + + +int +mint_keys_update_signkeys () +{ + struct GNUNET_TIME_Relative signkey_duration; + struct GNUNET_TIME_Absolute anchor; + char *signkey_dir; + + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_keys", "signkey_duration", &signkey_duration)) + { + fprintf (stderr, "Can't read config value mint_keys.signkey_duration\n"); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (signkey_duration, rel_value_us); + signkey_dir = get_signkey_dir (); + // make sure the directory exists + if (GNUNET_OK != GNUNET_DISK_directory_create (signkey_dir)) + { + fprintf (stderr, "Cant create signkey dir\n"); + return GNUNET_SYSERR; + } + + get_anchor (signkey_dir, signkey_duration, GNUNET_TIME_UNIT_ZERO, &anchor); + + while (anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) { + char *skf; + skf = get_signkey_file (anchor); + if (GNUNET_YES != GNUNET_DISK_file_test (skf)) + { + struct TALER_MINT_SignKeyIssue signkey_issue; + ssize_t nwrite; + printf ("Generating signing key for %s.\n", GNUNET_STRINGS_absolute_time_to_string (anchor)); + create_signkey_issue (anchor, signkey_duration, &signkey_issue); + nwrite = GNUNET_DISK_fn_write (skf, &signkey_issue, sizeof (struct TALER_MINT_SignKeyIssue), + (GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_USER_READ)); + if (nwrite != sizeof (struct TALER_MINT_SignKeyIssue)) + { + fprintf (stderr, "Can't write to file '%s'\n", skf); + return GNUNET_SYSERR; + } + } + else if (GNUNET_OK != check_signkey_valid (skf)) + { + return GNUNET_SYSERR; + } + anchor = GNUNET_TIME_absolute_add (anchor, signkey_duration); + } + return GNUNET_OK; +} + + +int +get_cointype_params (const char *ct, struct CoinTypeParams *params) +{ + const char *dir; + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_denom_duration_withdraw", ct, ¶ms->duration_withdraw)) + { + fprintf (stderr, "Withdraw duration not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (params->duration_withdraw, rel_value_us); + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_denom_duration_spend", ct, ¶ms->duration_spend)) + { + fprintf (stderr, "Spend duration not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (params->duration_spend, rel_value_us); + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_denom_duration_overlap", ct, ¶ms->duration_overlap)) + { + fprintf (stderr, "Overlap duration not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (params->duration_overlap, rel_value_us); + + if (GNUNET_OK != config_get_denom ("mint_denom_value", ct, ¶ms->value)) + { + fprintf (stderr, "Value not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != config_get_denom ("mint_denom_fee_withdraw", ct, ¶ms->fee_withdraw)) + { + fprintf (stderr, "Withdraw fee not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != config_get_denom ("mint_denom_fee_deposit", ct, ¶ms->fee_deposit)) + { + fprintf (stderr, "Deposit fee not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != config_get_denom ("mint_denom_fee_refresh", ct, ¶ms->fee_refresh)) + { + fprintf (stderr, "Deposit fee not given for coin type '%s'\n", ct); + return GNUNET_SYSERR; + } + + dir = get_cointype_dir (params); + get_anchor (dir, params->duration_spend, params->duration_overlap, ¶ms->anchor); + return GNUNET_OK; +} + + +static void +create_denomkey_issue (struct CoinTypeParams *params, struct TALER_MINT_DenomKeyIssue *dki) +{ + GNUNET_assert (NULL != (dki->denom_priv = TALER_RSA_key_create ())); + TALER_RSA_key_get_public (dki->denom_priv, &dki->denom_pub); + dki->master = *master_pub; + dki->start = GNUNET_TIME_absolute_hton (params->anchor); + dki->expire_withdraw = + GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, + params->duration_withdraw)); + dki->expire_spend = + GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, + params->duration_spend)); + dki->value = TALER_amount_hton (params->value); + dki->fee_withdraw = TALER_amount_hton (params->fee_withdraw); + dki->fee_deposit = TALER_amount_hton (params->fee_deposit); + dki->fee_refresh = TALER_amount_hton (params->fee_refresh); + + dki->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOM); + dki->purpose.size = htonl (sizeof (struct TALER_MINT_DenomKeyIssue) - offsetof (struct TALER_MINT_DenomKeyIssue, purpose)); + + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_sign (master_priv, &dki->purpose, &dki->signature)) + { + GNUNET_abort (); + } +} + + +static int +check_cointype_valid (const char *filename, struct CoinTypeParams *params) +{ + // FIXME: add real checks + return GNUNET_OK; +} + + +int +mint_keys_update_cointype (const char *coin_alias) +{ + struct CoinTypeParams p; + const char *cointype_dir; + + if (GNUNET_OK != get_cointype_params (coin_alias, &p)) + return GNUNET_SYSERR; + + cointype_dir = get_cointype_dir (&p); + if (GNUNET_OK != GNUNET_DISK_directory_create (cointype_dir)) + return GNUNET_SYSERR; + + while (p.anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) { + const char *dkf; + dkf = get_cointype_file (&p, p.anchor); + + if (GNUNET_YES != GNUNET_DISK_file_test (dkf)) + { + struct TALER_MINT_DenomKeyIssue denomkey_issue; + int ret; + printf ("Generating denomination key for type '%s', start %s.\n", + coin_alias, GNUNET_STRINGS_absolute_time_to_string (p.anchor)); + printf ("Target path: %s\n", dkf); + create_denomkey_issue (&p, &denomkey_issue); + ret = TALER_MINT_write_denom_key (dkf, &denomkey_issue); + TALER_RSA_key_free (denomkey_issue.denom_priv); + if (GNUNET_OK != ret) + { + fprintf (stderr, "Can't write to file '%s'\n", dkf); + return GNUNET_SYSERR; + } + } + else if (GNUNET_OK != check_cointype_valid (dkf, &p)) + { + return GNUNET_SYSERR; + } + p.anchor = GNUNET_TIME_absolute_add (p.anchor, p.duration_spend); + p.anchor = GNUNET_TIME_absolute_subtract (p.anchor, p.duration_overlap); + } + return GNUNET_OK; +} + + +int +mint_keys_update_denomkeys () +{ + char *coin_types; + char *ct; + char *tok_ctx; + + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg, "mint_keys", "coin_types", &coin_types)) + { + fprintf (stderr, "mint_keys.coin_types not in configuration\n"); + return GNUNET_SYSERR; + } + + for (ct = strtok_r (coin_types, " ", &tok_ctx); + ct != NULL; + ct = strtok_r (NULL, " ", &tok_ctx)) + { + if (GNUNET_OK != mint_keys_update_cointype (ct)) + { + GNUNET_free (coin_types); + return GNUNET_SYSERR; + } + } + GNUNET_free (coin_types); + return GNUNET_OK; +} + + +static int +mint_keys_update () +{ + int ret; + struct GNUNET_TIME_Relative lookahead_sign; + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_keys", "lookahead_sign", &lookahead_sign)) + { + fprintf (stderr, "mint_keys.lookahead_sign not found\n"); + return GNUNET_SYSERR; + } + if (lookahead_sign.rel_value_us == 0) + { + fprintf (stderr, "mint_keys.lookahead_sign must not be zero\n"); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (lookahead_sign, rel_value_us); + lookahead_sign_stamp = GNUNET_TIME_absolute_add (now, lookahead_sign); + + ret = mint_keys_update_signkeys (); + if (GNUNET_OK != ret) + return GNUNET_SYSERR; + + return mint_keys_update_denomkeys (); +} + + +/** + * The main function of the keyup tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"), + {'m', "master-key", "FILE", + "master key file (private key)", 1, + &GNUNET_GETOPT_set_filename, &masterkeyfile}, + {'d', "mint-dir", "DIR", + "mint directory with keys to update", 1, + &GNUNET_GETOPT_set_filename, &mintdir}, + {'t', "time", "TIMESTAMP", + "pretend it is a different time for the update", 0, + &GNUNET_GETOPT_set_string, &pretend_time_str}, + GNUNET_GETOPT_OPTION_END + }; + + GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("taler-mint-keyup", "WARNING", NULL)); + + if (GNUNET_GETOPT_run ("taler-mint-keyup", options, argc, argv) < 0) + return 1; + if (NULL == mintdir) + { + fprintf (stderr, "mint directory not given\n"); + return 1; + } + + if (NULL != pretend_time_str) + { + if (GNUNET_OK != GNUNET_STRINGS_fancy_time_to_absolute (pretend_time_str, &now)) + { + fprintf (stderr, "timestamp invalid\n"); + return 1; + } + } + else + { + now = GNUNET_TIME_absolute_get (); + } + ROUND_TO_SECS (now, abs_value_us); + + kcfg = TALER_MINT_config_load (mintdir); + if (NULL == kcfg) + { + fprintf (stderr, "can't load mint configuration\n"); + return 1; + } + + if (NULL == masterkeyfile) + { + fprintf (stderr, "master key file not given\n"); + return 1; + } + master_priv = GNUNET_CRYPTO_eddsa_key_create_from_file (masterkeyfile); + if (NULL == master_priv) + { + fprintf (stderr, "master key invalid\n"); + return 1; + } + + master_pub = GNUNET_new (struct GNUNET_CRYPTO_EddsaPublicKey); + GNUNET_CRYPTO_eddsa_key_get_public (master_priv, master_pub); + + // check if key from file matches the one from the configuration + { + struct GNUNET_CRYPTO_EddsaPublicKey master_pub_from_cfg; + if (GNUNET_OK != TALER_configuration_get_data (kcfg, "mint", "master_pub", + &master_pub_from_cfg, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) + { + fprintf (stderr, "master key missing in configuration (mint.master_pub)\n"); + return 1; + } + if (0 != memcmp (master_pub, &master_pub_from_cfg, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) + { + fprintf (stderr, "Mismatch between key from mint configuration and master private key file from command line.\n"); + return 1; + } + } + + if (GNUNET_OK != mint_keys_update ()) + return 1; + return 0; +} + -- cgit v1.2.3