From 209076ebd35458aaf61f4a8a4b71e127e4eda440 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 7 Apr 2017 22:37:00 +0200 Subject: implement denomination key revocation logic in exchangedb and taler-exchange-keyup (part of #3887) --- doc/taler-exchange-keyup.1 | 5 +- src/exchange-tools/taler-exchange-keycheck.c | 20 +++- src/exchange-tools/taler-exchange-keyup.c | 132 +++++++++++++++++++++++-- src/exchange-tools/taler-exchange-reservemod.c | 8 +- src/exchange/taler-exchange-httpd_keystate.c | 5 +- src/exchangedb/exchangedb_denomkeys.c | 127 +++++++++++++++++++++++- src/include/taler_exchangedb_lib.h | 27 ++++- src/include/taler_signatures.h | 22 +++++ 8 files changed, 329 insertions(+), 17 deletions(-) diff --git a/doc/taler-exchange-keyup.1 b/doc/taler-exchange-keyup.1 index 541c10ec8..7859d8a89 100644 --- a/doc/taler-exchange-keyup.1 +++ b/doc/taler-exchange-keyup.1 @@ -1,4 +1,4 @@ -.TH TALER\-EXCHANGE\-KEYUP 1 "Apr 22, 2015" "GNU Taler" +.TH TALER\-EXCHANGE\-KEYUP 1 "Apr 7, 2017" "GNU Taler" .SH NAME taler\-exchange\-keyup \- Setup Taler exchange denomination and signing keys. @@ -25,6 +25,9 @@ Location of the private EdDSA offline master key of the exchange. .IP "\-o FILE, \-\-ouptut=FILE" Where to write a denomination key signing request file to be given to the auditor. .B +.IP "\-r DKH, \-\-revoke=DKH" +Revoke the denomination key where the denomination public key's hash is DKH. +.B .IP "\-t TIMESTAMP, \-\-time=TIMESTAMP" Operate as if the current time was TIMESTAMP. .B diff --git a/src/exchange-tools/taler-exchange-keycheck.c b/src/exchange-tools/taler-exchange-keycheck.c index ee5f0c420..3286cff4c 100644 --- a/src/exchange-tools/taler-exchange-keycheck.c +++ b/src/exchange-tools/taler-exchange-keycheck.c @@ -122,6 +122,7 @@ exchange_signkeys_check () * @param cls closure (NULL) * @param dki the denomination key * @param alias coin alias + * @param was_revoked #GNUNET_YES if the @a dki was revoked and wallets should trigger /payback * @return #GNUNET_OK to continue to iterate, * #GNUNET_NO to stop iteration with no error, * #GNUNET_SYSERR to abort iteration with error! @@ -129,7 +130,8 @@ exchange_signkeys_check () static int denomkeys_iter (void *cls, const char *alias, - const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki) + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki, + int was_revoked) { struct GNUNET_HashCode hc; @@ -190,7 +192,23 @@ denomkeys_iter (void *cls, static int exchange_denomkeys_check () { + struct TALER_MasterPublicKeyP 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"); + global_ret = 1; + return GNUNET_NO; + } if (0 > TALER_EXCHANGEDB_denomination_keys_iterate (exchange_directory, + &master_public_key_from_cfg, &denomkeys_iter, NULL)) return GNUNET_NO; diff --git a/src/exchange-tools/taler-exchange-keyup.c b/src/exchange-tools/taler-exchange-keyup.c index 2290419aa..abf8793c7 100644 --- a/src/exchange-tools/taler-exchange-keyup.c +++ b/src/exchange-tools/taler-exchange-keyup.c @@ -220,6 +220,11 @@ static struct GNUNET_TIME_Absolute lookahead_sign_stamp; */ static struct GNUNET_TIME_Relative max_duration_spend; +/** + * Revoke denomination key identified by this hash (if non-zero). + */ +static struct GNUNET_HashCode revoke_dkh; + /** * Return value from main(). */ @@ -1018,6 +1023,106 @@ create_wire_fees () } +/** + * Closure for functions processing a request to revoke a denomination + * public key and request the wallets to initiate /payback. + */ +struct RevokeClosure +{ + /** + * Hash of the denomination public key to revoke. + */ + const struct GNUNET_HashCode *hc; + + /** + * Base directory for keys. + */ + char *basedir; + + /** + * Set to #GNUNET_OK if we found a matching key, + * Set to #GNUNET_SYSERR on error. + */ + int ok; +}; + + + +/** + * Revoke denomination keys matching the given hash. + * + * @param cls a `struct RevokeClosure` with information about what to revoke + * @param dki the denomination key + * @param alias coin alias + * @param was_revoked #GNUNET_YES if the @a dki was revoked and wallets should trigger /payback + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +static int +exchange_keys_revoke_by_dki (void *cls, + const char *alias, + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki, + int was_revoked) +{ + struct RevokeClosure *rc = cls; + + if (GNUNET_YES == was_revoked) + return GNUNET_OK; /* refuse to do it twice */ + if (0 != memcmp (rc->hc, + &dki->issue.properties.denom_hash, + sizeof (struct GNUNET_HashCode))) + return GNUNET_OK; + rc->ok = GNUNET_OK; + if (GNUNET_OK != + TALER_EXCHANGEDB_denomination_key_revoke (rc->basedir, + alias, + dki, + &master_priv)) + { + rc->ok = GNUNET_SYSERR; + return GNUNET_SYSERR; + } + return GNUNET_NO; +} + + +/** + * Revoke the denomination key matching @a hc and request /payback to be + * initiated. + * + * @param hc denomination key hash to revoke + * @return #GNUNET_OK on success, + * #GNUNET_NO if @a hc was not found + * #GNUNET_SYSERR on error + */ +static int +revoke_denomination (const struct GNUNET_HashCode *hc) +{ + struct RevokeClosure rc; + + rc.hc = hc; + rc.ok = GNUNET_NO; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (kcfg, + "exchange", + "KEYDIR", + &rc.basedir)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "KEYDIR"); + return GNUNET_SYSERR; + } + TALER_EXCHANGEDB_denomination_keys_iterate (rc.basedir, + &master_public_key, + &exchange_keys_revoke_by_dki, + &rc); + GNUNET_free (rc.basedir); + return rc.ok; +} + + /** * Main function that will be run. * @@ -1032,6 +1137,7 @@ run (void *cls, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *cfg) { + static struct GNUNET_HashCode zero; struct GNUNET_TIME_Relative lookahead_sign; struct GNUNET_CRYPTO_EddsaPrivateKey *eddsa_priv; @@ -1119,7 +1225,7 @@ run (void *cls, /* check if key from file matches the one from the configuration */ { - struct GNUNET_CRYPTO_EddsaPublicKey master_public_key_from_cfg; + struct TALER_MasterPublicKeyP master_public_key_from_cfg; if (GNUNET_OK != GNUNET_CONFIGURATION_get_data (kcfg, @@ -1137,7 +1243,7 @@ run (void *cls, if (0 != memcmp (&master_public_key, &master_public_key_from_cfg, - sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) + sizeof (struct TALER_MasterPublicKeyP))) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, "exchange", @@ -1190,6 +1296,15 @@ run (void *cls, global_ret = 1; return; } + if ( (0 != memcmp (&zero, + &revoke_dkh, + sizeof (zero))) && + (GNUNET_OK != + revoke_denomination (&revoke_dkh)) ) + { + global_ret = 1; + return; + } } @@ -1226,11 +1341,16 @@ main (int argc, "FILENAME", "auditor denomination key signing request file to create", &auditorrequestfile), + GNUNET_GETOPT_option_base32_auto ('r', + "revoke", + "DKH", + "revoke denomination key hash (DKH) and request wallets to initiate /payback", + &revoke_dkh), GNUNET_GETOPT_option_absolute_time ('t', - "time", - "TIMESTAMP", - "pretend it is a different time for the update", - &now), + "time", + "TIMESTAMP", + "pretend it is a different time for the update", + &now), GNUNET_GETOPT_OPTION_END }; diff --git a/src/exchange-tools/taler-exchange-reservemod.c b/src/exchange-tools/taler-exchange-reservemod.c index 12d0f9c07..01114a9b1 100644 --- a/src/exchange-tools/taler-exchange-reservemod.c +++ b/src/exchange-tools/taler-exchange-reservemod.c @@ -188,10 +188,10 @@ main (int argc, char *const *argv) GNUNET_GETOPT_option_help ("Deposit funds into a Taler reserve"), GNUNET_GETOPT_option_mandatory (GNUNET_GETOPT_option_base32_auto ('R', - "reserve", - "KEY", - "reserve (public key) to modify", - &reserve_pub)), + "reserve", + "KEY", + "reserve (public key) to modify", + &reserve_pub)), GNUNET_GETOPT_OPTION_END }; diff --git a/src/exchange/taler-exchange-httpd_keystate.c b/src/exchange/taler-exchange-httpd_keystate.c index e754eb4fc..164f61abb 100644 --- a/src/exchange/taler-exchange-httpd_keystate.c +++ b/src/exchange/taler-exchange-httpd_keystate.c @@ -210,6 +210,7 @@ TALER_EXCHANGE_conf_duration_provide () * @param cls closure * @param dki the denomination key issue * @param alias coin alias + * @param was_revoked #GNUNET_YES if @a dki has been revoked * @return #GNUNET_OK to continue to iterate, * #GNUNET_NO to stop iteration with no error, * #GNUNET_SYSERR to abort iteration with error! @@ -217,7 +218,8 @@ TALER_EXCHANGE_conf_duration_provide () static int reload_keys_denom_iter (void *cls, const char *alias, - const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki) + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki, + int was_revoked) { struct TEH_KS_StateHandle *ctx = cls; struct GNUNET_TIME_Absolute now; @@ -674,6 +676,7 @@ TEH_KS_acquire_ (const char *location) "Loading keys from `%s'\n", TEH_exchange_directory); if (-1 == TALER_EXCHANGEDB_denomination_keys_iterate (TEH_exchange_directory, + &TEH_master_public_key, &reload_keys_denom_iter, key_state)) { diff --git a/src/exchangedb/exchangedb_denomkeys.c b/src/exchangedb/exchangedb_denomkeys.c index de8dfa6f2..bfc3ab8eb 100644 --- a/src/exchangedb/exchangedb_denomkeys.c +++ b/src/exchangedb/exchangedb_denomkeys.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015, 2016 Inria & GNUnet e.V. + Copyright (C) 2014-2017 Inria & 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 @@ -25,6 +25,68 @@ #include "taler_exchangedb_lib.h" +/** + * Mark the given denomination key as revoked and request the wallets + * to initiate /payback. + * + * @param exchange_base_dir base directory for the exchange, + * the signing keys must be in the #TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS + * subdirectory + * @param alias coin alias + * @param dki the denomination key to revoke + * @param mpriv master private key to sign + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure. + */ +int +TALER_EXCHANGEDB_denomination_key_revoke (const char *exchange_base_dir, + const char *alias, + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki, + const struct TALER_MasterPrivateKeyP *mpriv) +{ + struct GNUNET_TIME_Absolute start; + struct TALER_MasterDenominationKeyRevocation rm; + struct TALER_MasterSignatureP msig; + char *fn; + struct GNUNET_DISK_FileHandle *fh; + ssize_t wrote; + int ret; + + ret = GNUNET_SYSERR; + start = GNUNET_TIME_absolute_ntoh (dki->issue.properties.start); + GNUNET_asprintf (&fn, + "%s" DIR_SEPARATOR_STR "%s" DIR_SEPARATOR_STR "%llu.rev", + exchange_base_dir, + alias, + (unsigned long long) start.abs_value_us); + + rm.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED); + rm.purpose.size = htonl (sizeof (rm)); + rm.h_denom_pub = dki->issue.properties.denom_hash; + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&mpriv->eddsa_priv, + &rm.purpose, + &msig.eddsa_signature)); + if (NULL == (fh = GNUNET_DISK_file_open + (fn, + 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 (GNUNET_SYSERR == + (wrote = GNUNET_DISK_file_write (fh, + &msig, + sizeof (msig)))) + goto cleanup; + if (wrote != sizeof (msig)) + goto cleanup; + ret = GNUNET_OK; +cleanup: + if (NULL != fh) + (void) GNUNET_DISK_file_close (fh); + GNUNET_free (fn); + return ret; +} + + /** * Import a denomination key from the given file. * @@ -157,6 +219,11 @@ struct DenomkeysIterateContext */ const char *alias; + /** + * Master public key to use to validate revocations. + */ + const struct TALER_MasterPublicKeyP *master_pub; + /** * Function to call on each denomination key. */ @@ -187,6 +254,15 @@ denomkeys_iterate_keydir_iter (void *cls, struct DenomkeysIterateContext *dic = cls; struct TALER_EXCHANGEDB_DenominationKeyIssueInformation issue; int ret; + char *rev; + struct TALER_MasterSignatureP msig; + struct TALER_MasterDenominationKeyRevocation rm; + int revoked; + + if ( (strlen(filename) > strlen (".rev")) && + (0 == strcmp (&filename[strlen(filename) - strlen (".rev")], + ".rev")) ) + return GNUNET_OK; /* ignore revocation files _here_; we'll try for them just below */ memset (&issue, 0, sizeof (issue)); if (GNUNET_OK != @@ -198,9 +274,53 @@ denomkeys_iterate_keydir_iter (void *cls, filename); return GNUNET_OK; } + /* check for revocation file */ + GNUNET_asprintf (&rev, + "%s.rev", + filename); + revoked = GNUNET_NO; + if (GNUNET_YES == GNUNET_DISK_file_test (rev)) + { + /* Check if revocation is valid... */ + if (sizeof (msig) != + GNUNET_DISK_fn_read (rev, + &msig, + sizeof (msig))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Invalid revocation file `%s' found and ignored (bad size)\n"), + rev); + } + else + { + rm.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED); + rm.purpose.size = htonl (sizeof (rm)); + rm.h_denom_pub = issue.issue.properties.denom_hash; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED, + &rm.purpose, + &msig.eddsa_signature, + &dic->master_pub->eddsa_pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Invalid revocation file `%s' found and ignored (bad signature)\n"), + rev); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Denomination key `%s' was revoked!\n", + filename); + revoked = GNUNET_YES; + } + } + + } + GNUNET_free (rev); ret = dic->it (dic->it_cls, dic->alias, - &issue); + &issue, + revoked); GNUNET_CRYPTO_rsa_private_key_free (issue.denom_priv.rsa_private_key); GNUNET_CRYPTO_rsa_public_key_free (issue.denom_pub.rsa_public_key); return ret; @@ -238,6 +358,7 @@ denomkeys_iterate_topdir_iter (void *cls, * @param exchange_base_dir base directory for the exchange, * the signing keys must be in the #TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS * subdirectory + * @param master_pub master public key (used to check revocations) * @param it function to call on each denomination key found * @param it_cls closure for @a it * @return -1 on error, 0 if no files were found, otherwise @@ -247,6 +368,7 @@ denomkeys_iterate_topdir_iter (void *cls, */ int TALER_EXCHANGEDB_denomination_keys_iterate (const char *exchange_base_dir, + const struct TALER_MasterPublicKeyP *master_pub, TALER_EXCHANGEDB_DenominationKeyIterator it, void *it_cls) { @@ -257,6 +379,7 @@ TALER_EXCHANGEDB_denomination_keys_iterate (const char *exchange_base_dir, GNUNET_asprintf (&dir, "%s" DIR_SEPARATOR_STR TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS, exchange_base_dir); + dic.master_pub = master_pub; dic.it = it; dic.it_cls = it_cls; ret = GNUNET_DISK_directory_scan (dir, diff --git a/src/include/taler_exchangedb_lib.h b/src/include/taler_exchangedb_lib.h index e4057470f..d70270e8e 100644 --- a/src/include/taler_exchangedb_lib.h +++ b/src/include/taler_exchangedb_lib.h @@ -160,8 +160,9 @@ TALER_EXCHANGEDB_signing_key_write (const char *exchange_base_dir, * @brief Iterator over denomination keys. * * @param cls closure - * @param dki the denomination key * @param alias coin alias + * @param dki the denomination key + * @param was_revoked #GNUNET_YES if the @a dki was revoked and wallets should trigger /payback * @return #GNUNET_OK to continue to iterate, * #GNUNET_NO to stop iteration with no error, * #GNUNET_SYSERR to abort iteration with error! @@ -169,7 +170,8 @@ TALER_EXCHANGEDB_signing_key_write (const char *exchange_base_dir, typedef int (*TALER_EXCHANGEDB_DenominationKeyIterator)(void *cls, const char *alias, - const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki); + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki, + int was_revoked); /** @@ -178,6 +180,7 @@ typedef int * @param exchange_base_dir base directory for the exchange, * the signing keys must be in the #TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS * subdirectory + * @param master_pub master public key (used to check revocations) * @param it function to call on each denomination key found * @param it_cls closure for @a it * @return -1 on error, 0 if no files were found, otherwise @@ -187,10 +190,30 @@ typedef int */ int TALER_EXCHANGEDB_denomination_keys_iterate (const char *exchange_base_dir, + const struct TALER_MasterPublicKeyP *master_pub, TALER_EXCHANGEDB_DenominationKeyIterator it, void *it_cls); +/** + * Mark the given denomination key as revoked and request the wallets + * to initiate /payback. + * + * @param exchange_base_dir base directory for the exchange, + * the signing keys must be in the #TALER_EXCHANGEDB_DIR_DENOMINATION_KEYS + * subdirectory + * @param alias coin alias + * @param dki the denomination key to revoke + * @param mpriv master private key to sign + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure. + */ +int +TALER_EXCHANGEDB_denomination_key_revoke (const char *exchange_base_dir, + const char *alias, + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki, + const struct TALER_MasterPrivateKeyP *mpriv); + + /** * Exports a denomination key to the given file. * diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h index c58ea1915..d4c5d864d 100644 --- a/src/include/taler_signatures.h +++ b/src/include/taler_signatures.h @@ -83,6 +83,10 @@ */ #define TALER_SIGNATURE_MASTER_WIRE_FEES 1028 +/** + * The given revocation key was revoked and must no longer be used. + */ +#define TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED 1029 /*********************************************/ /* Exchange online signatures (with signing key) */ @@ -937,6 +941,24 @@ struct TALER_MasterWireFeePS }; +/** + * @brief Message confirming that a denomination key was revoked. + */ +struct TALER_MasterDenominationKeyRevocation +{ + /** + * Purpose is #TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash of the denomination key. + */ + struct GNUNET_HashCode h_denom_pub; + +}; + + /** * @brief Format used to generate the signature on a request to obtain * the wire transfer identifier associated with a deposit. -- cgit v1.2.3