From 2f2930f1ba0f1708fc4455c66173fd61188a3369 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 31 Oct 2017 14:02:54 +0100 Subject: major API refactoring, adding planchet generation and coin extraction APIs to the Taler crypto library, thereby simplifying code in withdraw, refresh, tipping, payback and testcases; slight API incompatibilities to previous versions are introduced --- src/util/crypto.c | 137 ++++++++++++++++++++++++++++++++++++++++++------- src/util/test_crypto.c | 51 ++++++++++++++++-- 2 files changed, 165 insertions(+), 23 deletions(-) (limited to 'src/util') diff --git a/src/util/crypto.c b/src/util/crypto.c index 378b73dc4..efc74850d 100644 --- a/src/util/crypto.c +++ b/src/util/crypto.c @@ -170,24 +170,50 @@ TALER_link_recover_transfer_secret (const struct TALER_TransferPublicKeyP *trans } +/** + * Set the bits in the private EdDSA key so that they match + * the specification. + * + * @param[in,out] pk private key to patch + */ +static void +patch_private_key (struct GNUNET_CRYPTO_EddsaPrivateKey *pk) +{ + uint8_t *p = (uint8_t *) pk; + + /* Taken from like 170-172 of libgcrypt/cipher/ecc.c + * We note that libgcrypt stores the private key in the reverse order + * from many Ed25519 implementatons. */ + p[0] &= 0x7f; /* Clear bit 255. */ + p[0] |= 0x40; /* Set bit 254. */ + p[31] &= 0xf8; /* Clear bits 2..0 so that d mod 8 == 0 */ + + /* FIXME: Run GNUNET_CRYPTO_ecdhe_key_create several times and inspect + * the output to verify that the same bits are set and cleared. + * Is it worth also adding a test case that runs gcry_pk_testkey on + * this key after first parsing it into libgcrypt's s-expression mess + * ala decode_private_eddsa_key from gnunet/src/util/crypto_ecc.c? + * It'd run check_secret_key but not test_keys from libgcrypt/cipher/ecc.c */ +} + + /** * Setup information for a fresh coin. * * @param secret_seed seed to use for KDF to derive coin keys * @param coin_num_salt number of the coin to include in KDF - * @param[out] fc value to initialize + * @param[out] ps value to initialize */ void TALER_planchet_setup_refresh (const struct TALER_TransferSecretP *secret_seed, - unsigned int coin_num_salt, - struct TALER_PlanchetSecretsP *fc) + unsigned int coin_num_salt, + struct TALER_PlanchetSecretsP *ps) { uint32_t be_salt = htonl (coin_num_salt); - uint8_t *p; GNUNET_assert (GNUNET_OK == - GNUNET_CRYPTO_kdf (fc, - sizeof (*fc), + GNUNET_CRYPTO_kdf (ps, + sizeof (*ps), &be_salt, sizeof (be_salt), secret_seed, @@ -195,24 +221,97 @@ TALER_planchet_setup_refresh (const struct TALER_TransferSecretP *secret_seed, "taler-coin-derivation", strlen ("taler-coin-derivation"), NULL, 0)); + patch_private_key (&ps->coin_priv.eddsa_priv); +} - /* Taken from like 170-172 of libgcrypt/cipher/ecc.c - * We note that libgcrypt stores the private key in the reverse order - * from many Ed25519 implementatons. */ - p = (uint8_t *) &(fc->coin_priv); - p[0] &= 0x7f; /* Clear bit 255. */ - p[0] |= 0x40; /* Set bit 254. */ - p[31] &= 0xf8; /* Clear bits 2..0 so that d mod 8 == 0 */ - /* FIXME: Run GNUNET_CRYPTO_ecdhe_key_create several times and inspect - * the output to verify that the same bits are set and cleared. - * Is it worth also adding a test case that runs gcry_pk_testkey on - * this key after first parsing it into libgcrypt's s-expression mess - * ala decode_private_eddsa_key from gnunet/src/util/crypto_ecc.c? - * It'd run check_secret_key but not test_keys from libgcrypt/cipher/ecc.c */ +/** + * Setup information for a fresh coin. + * + * @param[out] ps value to initialize + */ +void +TALER_planchet_setup_random (struct TALER_PlanchetSecretsP *ps) +{ + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, + ps, + sizeof (*ps)); + patch_private_key (&ps->coin_priv.eddsa_priv); +} + + +/** + * Prepare a planchet for tipping. Creates and blinds a coin. + * + * @param dk denomination key for the coin to be created + * @param ps secret planchet internals (for #TALER_planchet_to_coin) + * @param[out] pd set to the planchet detail for TALER_MERCHANT_tip_pickup() and + * other withdraw operations + * @return #GNUNET_OK on success + */ +int +TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk, + const struct TALER_PlanchetSecretsP *ps, + struct TALER_PlanchetDetail *pd) +{ + struct TALER_CoinSpendPublicKeyP coin_pub; + + GNUNET_CRYPTO_eddsa_key_get_public (&ps->coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), + &pd->c_hash); + if (GNUNET_YES != + GNUNET_CRYPTO_rsa_blind (&pd->c_hash, + &ps->blinding_key.bks, + dk->rsa_public_key, + &pd->coin_ev, + &pd->coin_ev_size)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_rsa_public_key_hash (dk->rsa_public_key, + &pd->denom_pub_hash); + return GNUNET_OK; } +/** + * Obtain a coin from the planchet's secrets and the blind signature + * of the exchange. + * + * @param dk denomination key, must match what was given to #TALER_planchet_prepare() + * @param blind_sig blind signature from the exchange + * @param ps secrets from #TALER_planchet_prepare() + * @param c_hash hash of the coin's public key for verification of the signature + * @param[out] coin set to the details of the fresh coin + * @return #GNUNET_OK on success + */ +int +TALER_planchet_to_coin (const struct TALER_DenominationPublicKey *dk, + const struct GNUNET_CRYPTO_RsaSignature *blind_sig, + const struct TALER_PlanchetSecretsP *ps, + const struct GNUNET_HashCode *c_hash, + struct TALER_FreshCoin *coin) +{ + struct GNUNET_CRYPTO_RsaSignature *sig; + sig = GNUNET_CRYPTO_rsa_unblind (blind_sig, + &ps->blinding_key.bks, + dk->rsa_public_key); + if (GNUNET_OK != + GNUNET_CRYPTO_rsa_verify (c_hash, + sig, + dk->rsa_public_key)) + { + GNUNET_break_op (0); + GNUNET_CRYPTO_rsa_signature_free (sig); + return GNUNET_SYSERR; + } + coin->sig.rsa_signature = sig; + coin->coin_priv = ps->coin_priv; + return GNUNET_OK; +} /* end of crypto.c */ diff --git a/src/util/test_crypto.c b/src/util/test_crypto.c index 953ad94df..4713b3a35 100644 --- a/src/util/test_crypto.c +++ b/src/util/test_crypto.c @@ -71,11 +71,11 @@ test_high_level () &secret2, sizeof (secret))); TALER_planchet_setup_refresh (&secret, - 0, - &fc1); + 0, + &fc1); TALER_planchet_setup_refresh (&secret, - 1, - &fc2); + 1, + &fc2); GNUNET_assert (0 != memcmp (&fc1, &fc2, @@ -84,12 +84,55 @@ test_high_level () } +/** + * Test the basic planchet functionality of creating a fresh planchet + * and extracting the respective signature. + * + * @return 0 on success + */ +static int +test_planchets () +{ + struct TALER_PlanchetSecretsP ps; + struct TALER_DenominationPrivateKey dk_priv; + struct TALER_DenominationPublicKey dk_pub; + struct TALER_PlanchetDetail pd; + struct GNUNET_CRYPTO_RsaSignature *blind_sig; + struct TALER_FreshCoin coin; + + dk_priv.rsa_private_key = GNUNET_CRYPTO_rsa_private_key_create (1024); + dk_pub.rsa_public_key = GNUNET_CRYPTO_rsa_private_key_get_public (dk_priv.rsa_private_key); + TALER_planchet_setup_random (&ps); + GNUNET_assert (GNUNET_OK == + TALER_planchet_prepare (&dk_pub, + &ps, + &pd)); + blind_sig = GNUNET_CRYPTO_rsa_sign_blinded (dk_priv.rsa_private_key, + pd.coin_ev, + pd.coin_ev_size); + GNUNET_assert (NULL != blind_sig); + GNUNET_assert (GNUNET_OK == + TALER_planchet_to_coin (&dk_pub, + blind_sig, + &ps, + &pd.c_hash, + &coin)); + GNUNET_CRYPTO_rsa_signature_free (blind_sig); + GNUNET_CRYPTO_rsa_signature_free (coin.sig.rsa_signature); + GNUNET_CRYPTO_rsa_private_key_free (dk_priv.rsa_private_key); + GNUNET_CRYPTO_rsa_public_key_free (dk_pub.rsa_public_key); + return 0; +} + + int main(int argc, const char *const argv[]) { if (0 != test_high_level ()) return 1; + if (0 != test_planchets ()) + return 1; return 0; } -- cgit v1.2.3