/* This file is part of TALER (C) 2020, 2021 Taler Systems SA 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, see */ /** * @file util/test_helper_rsa.c * @brief Tests for RSA crypto helper * @author Christian Grothoff */ #include "platform.h" #include "taler_util.h" /** * Configuration has 1 minute duration and 5 minutes lookahead, but * we do not get 'revocations' for expired keys. So this must be * large enough to deal with key rotation during the runtime of * the benchmark. */ #define MAX_KEYS 1024 /** * How many random key revocations should we test? */ #define NUM_REVOKES 3 /** * How many iterations of the successful signing test should we run? */ #define NUM_SIGN_TESTS 5 /** * How many iterations of the successful signing test should we run * during the benchmark phase? */ #define NUM_SIGN_PERFS 100 /** * How many parallel clients should we use for the parallel * benchmark? (> 500 may cause problems with the max open FD number limit). */ #define NUM_CORES 8 /** * Number of keys currently in #keys. */ static unsigned int num_keys; /** * Keys currently managed by the helper. */ struct KeyData { /** * Validity start point. */ struct GNUNET_TIME_Absolute start_time; /** * Key expires for signing at @e start_time plus this value. */ struct GNUNET_TIME_Relative validity_duration; /** * Hash of the public key. */ struct TALER_RsaPubHashP h_rsa; /** * Full public key. */ struct TALER_DenominationPublicKey denom_pub; /** * Is this key currently valid? */ bool valid; /** * Did the test driver revoke this key? */ bool revoked; }; /** * Array of all the keys we got from the helper. */ static struct KeyData keys[MAX_KEYS]; /** * Function called with information about available keys for signing. Usually * only called once per key upon connect. Also called again in case a key is * being revoked, in that case with an @a end_time of zero. Stores the keys * status in #keys. * * @param cls closure, NULL * @param section_name name of the denomination type in the configuration; * NULL if the key has been revoked or purged * @param start_time when does the key become available for signing; * zero if the key has been revoked or purged * @param validity_duration how long does the key remain available for signing; * zero if the key has been revoked or purged * @param h_rsa hash of the @a denom_pub that is available (or was purged) * @param denom_pub the public key itself, NULL if the key was revoked or purged * @param sm_pub public key of the security module, NULL if the key was revoked or purged * @param sm_sig signature from the security module, NULL if the key was revoked or purged * The signature was already verified against @a sm_pub. */ static void key_cb (void *cls, const char *section_name, struct GNUNET_TIME_Absolute start_time, struct GNUNET_TIME_Relative validity_duration, const struct TALER_RsaPubHashP *h_rsa, const struct TALER_DenominationPublicKey *denom_pub, const struct TALER_SecurityModulePublicKeyP *sm_pub, const struct TALER_SecurityModuleSignatureP *sm_sig) { (void) sm_pub; (void) sm_sig; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Key notification about key %s in `%s'\n", GNUNET_h2s (&h_rsa->hash), section_name); if (0 == validity_duration.rel_value_us) { bool found = false; GNUNET_break (NULL == denom_pub); GNUNET_break (NULL == section_name); for (unsigned int i = 0; i 0); num_keys--; found = true; break; } if (! found) GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error: helper announced expiration of unknown key!\n"); return; } GNUNET_break (NULL != denom_pub); for (unsigned int i = 0; i GNUNET_TIME_UNIT_SECONDS.rel_value_us) { /* key worked too early */ GNUNET_break (0); return 4; } if (GNUNET_TIME_absolute_get_duration (keys[i].start_time).rel_value_us > keys[i].validity_duration.rel_value_us) { /* key worked too later */ GNUNET_break (0); return 5; } { struct TALER_DenominationSignature rs; if (GNUNET_OK != TALER_denom_sig_unblind (&rs, &ds, &ps.blinding_key, &keys[i].denom_pub)) { GNUNET_break (0); return 6; } TALER_blinded_denom_sig_free (&ds); if (GNUNET_OK != TALER_denom_pub_verify (&keys[i].denom_pub, &rs, &c_hash)) { /* signature invalid */ GNUNET_break (0); TALER_denom_sig_free (&rs); return 7; } TALER_denom_sig_free (&rs); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received valid signature for key %s\n", GNUNET_h2s (&keys[i].h_rsa.hash)); success = true; break; case TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY: /* This 'failure' is expected, we're testing also for the error handling! */ if ( (0 == GNUNET_TIME_absolute_get_remaining ( keys[i].start_time).rel_value_us) && (GNUNET_TIME_absolute_get_duration ( keys[i].start_time).rel_value_us < keys[i].validity_duration.rel_value_us) ) { /* key should have worked! */ GNUNET_break (0); return 6; } break; default: /* unexpected error */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected error %d\n", ec); return 7; } } if (! success) { /* no valid key for signing found, also bad */ GNUNET_break (0); return 16; } /* check signing does not work if the key is unknown */ { struct TALER_RsaPubHashP rnd; GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, &rnd, sizeof (rnd)); ds = TALER_CRYPTO_helper_rsa_sign (dh, &rnd, "Hello", strlen ("Hello"), &ec); if (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN != ec) { if (TALER_EC_NONE == ec) TALER_blinded_denom_sig_free (&ds); GNUNET_break (0); return 17; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Signing with invalid key %s failed as desired\n", GNUNET_h2s (&rnd.hash)); } return 0; } /** * Benchmark signing logic. * * @param dh handle to the helper * @return 0 on success */ static int perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh, const char *type) { struct TALER_BlindedDenominationSignature ds; enum TALER_ErrorCode ec; struct GNUNET_TIME_Relative duration; struct TALER_PlanchetSecretsP ps; TALER_planchet_setup_random (&ps); duration = GNUNET_TIME_UNIT_ZERO; TALER_CRYPTO_helper_rsa_poll (dh); for (unsigned int j = 0; j GNUNET_TIME_UNIT_SECONDS.rel_value_us) continue; if (GNUNET_TIME_absolute_get_duration (keys[i].start_time).rel_value_us > keys[i].validity_duration.rel_value_us) continue; { struct TALER_CoinPubHash c_hash; struct TALER_PlanchetDetail pd; GNUNET_assert (GNUNET_YES == TALER_planchet_prepare (&keys[i].denom_pub, &ps, &c_hash, &pd)); /* use this key as long as it works */ while (1) { struct GNUNET_TIME_Absolute start = GNUNET_TIME_absolute_get (); struct GNUNET_TIME_Relative delay; ds = TALER_CRYPTO_helper_rsa_sign (dh, &keys[i].h_rsa, pd.coin_ev, pd.coin_ev_size, &ec); if (TALER_EC_NONE != ec) break; delay = GNUNET_TIME_absolute_get_duration (start); duration = GNUNET_TIME_relative_add (duration, delay); TALER_blinded_denom_sig_free (&ds); j++; if (NUM_SIGN_PERFS <= j) break; } GNUNET_free (pd.coin_ev); } } /* for i */ } /* for j */ fprintf (stderr, "%u (%s) signature operations took %s\n", (unsigned int) NUM_SIGN_PERFS, type, GNUNET_STRINGS_relative_time_to_string (duration, GNUNET_YES)); return 0; } /** * Parallel signing logic. * * @param esh handle to the helper * @return 0 on success */ static int par_signing (struct GNUNET_CONFIGURATION_Handle *cfg) { struct GNUNET_TIME_Absolute start; struct GNUNET_TIME_Relative duration; pid_t pids[NUM_CORES]; struct TALER_CRYPTO_RsaDenominationHelper *dh; start = GNUNET_TIME_absolute_get (); for (unsigned int i = 0; i 0); num_keys--; } GNUNET_CONFIGURATION_destroy (cfg); return ret; } int main (int argc, const char *const argv[]) { struct GNUNET_OS_Process *helper; char *libexec_dir; char *binary_name; int ret; enum GNUNET_OS_ProcessStatusType type; unsigned long code; (void) argc; (void) argv; GNUNET_log_setup ("test-helper-rsa", "WARNING", NULL); GNUNET_OS_init (TALER_project_data_default ()); libexec_dir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_BINDIR); GNUNET_asprintf (&binary_name, "%s/%s", libexec_dir, "taler-exchange-secmod-rsa"); GNUNET_free (libexec_dir); helper = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR, NULL, NULL, NULL, binary_name, binary_name, "-c", "test_helper_rsa.conf", "-L", "WARNING", NULL); if (NULL == helper) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "exec", binary_name); GNUNET_free (binary_name); return 77; } GNUNET_free (binary_name); ret = run_test (); GNUNET_OS_process_kill (helper, SIGTERM); if (GNUNET_OK != GNUNET_OS_process_wait_status (helper, &type, &code)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Helper process did not die voluntarily, killing hard\n"); GNUNET_OS_process_kill (helper, SIGKILL); ret = 4; } else if ( (GNUNET_OS_PROCESS_EXITED != type) || (0 != code) ) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Helper died with unexpected status %d/%d\n", (int) type, (int) code); ret = 5; } GNUNET_OS_process_destroy (helper); return ret; } /* end of test_helper_rsa.c */