From a227ee6d1bd979ae87ab9afda27f180c840313bf Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 28 Mar 2022 13:57:43 +0200 Subject: [PATCH] -first cut at contract encryption and decryption --- src/include/taler_exchange_service.h | 4 + .../exchange_api_purse_create_with_deposit.c | 25 +- src/util/Makefile.am | 3 + src/util/crypto_contract.c | 300 +++++++++++++++++- 4 files changed, 319 insertions(+), 13 deletions(-) diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index cf4624252..00c8c101f 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -4287,6 +4287,9 @@ struct TALER_EXCHANGE_PurseDeposit * @param purse_expiration when will the unmerged purse expire * @param num_deposits length of the @a deposits array * @param deposits array of deposits to make into the purse + * @param upload_contract true to upload the contract; must + * be FALSE for repeated calls to this API for the + * same purse (i.e. when adding more deposits). * @param cb function to call with the exchange's result * @param cb_cls closure for @a cb * @return the request handle; NULL upon error @@ -4301,6 +4304,7 @@ TALER_EXCHANGE_purse_create_with_deposit ( struct GNUNET_TIME_Timestamp purse_expiration, unsigned int num_deposits, const struct TALER_EXCHANGE_PurseDeposit *deposits, + bool upload_contract, TALER_EXCHANGE_PurseCreateDepositCallback cb, void *cb_cls); diff --git a/src/lib/exchange_api_purse_create_with_deposit.c b/src/lib/exchange_api_purse_create_with_deposit.c index 6765b7771..e39cb076f 100644 --- a/src/lib/exchange_api_purse_create_with_deposit.c +++ b/src/lib/exchange_api_purse_create_with_deposit.c @@ -202,6 +202,7 @@ TALER_EXCHANGE_purse_create_with_deposit ( struct GNUNET_TIME_Timestamp purse_expiration, unsigned int num_deposits, const struct TALER_EXCHANGE_PurseDeposit *deposits, + bool upload_contract, TALER_EXCHANGE_PurseCreateDepositCallback cb, void *cb_cls) { @@ -335,23 +336,25 @@ TALER_EXCHANGE_purse_create_with_deposit ( } GNUNET_free (url); { - void *econtract; - size_t econtract_size; + void *econtract = NULL; + size_t econtract_size = 0; - TALER_CRYPTO_contract_encrypt_for_merge (&purse_pub, - contract_priv, - merge_priv, - contract_terms, - &econtract, - &econtract_size); + if (upload_contract) + TALER_CRYPTO_contract_encrypt_for_merge (&purse_pub, + contract_priv, + merge_priv, + contract_terms, + &econtract, + &econtract_size); create_obj = GNUNET_JSON_PACK ( TALER_JSON_pack_amount ("amount", &purse_value_after_fees), GNUNET_JSON_pack_uint64 ("min_age", min_age), - GNUNET_JSON_pack_data_varsize ("econtract", - econtract, - econtract_size), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_varsize ("econtract", + econtract, + econtract_size)), GNUNET_JSON_pack_data_auto ("contract_pub", &contract_pub), GNUNET_JSON_pack_data_auto ("merge_pub", diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 8ef9e146d..b92c427b0 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -105,8 +105,11 @@ libtalerutil_la_SOURCES = \ libtalerutil_la_LIBADD = \ -lgnunetutil \ + -lsodium \ + -ljansson \ $(LIBGCRYPT_LIBS) \ -lmicrohttpd $(XLIB) \ + -lz \ -lm libtalerutil_la_LDFLAGS = \ diff --git a/src/util/crypto_contract.c b/src/util/crypto_contract.c index 7f684d80a..5117712ea 100644 --- a/src/util/crypto_contract.c +++ b/src/util/crypto_contract.c @@ -20,6 +20,191 @@ */ #include "platform.h" #include "taler_util.h" +#include +#include "taler_exchange_service.h" + + +/** + * Nonce used for encryption, 24 bytes. + */ +struct NonceP +{ + uint8_t nonce[crypto_secretbox_NONCEBYTES]; +}; + +/** + * Specifies a key used for symmetric encryption, 32 bytes. + */ +struct SymKeyP +{ + uint32_t key[8]; +}; + + +/** + * Compute @a key. + * + * @param key_material key for calculation + * @param key_m_len length of key + * @param nonce nonce for calculation + * @param salt salt value for calculation + * @param[out] key where to write the en-/description key + */ +static void +derive_key (const void *key_material, + size_t key_m_len, + const struct NonceP *nonce, + const char *salt, + struct SymKeyP *key) +{ + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (key, + sizeof (*key), + /* salt / XTS */ + nonce, + sizeof (*nonce), + /* ikm */ + key_material, + key_m_len, + /* info chunks */ + /* The "salt" passed here is actually not something random, + but a protocol-specific identifier string. Thus + we pass it as a context info to the HKDF */ + salt, + strlen (salt), + NULL, + 0)); +} + + +/** + * Encryption of data. + * + * @param nonce value to use for the nonce + * @param key key which is used to derive a key/iv pair from + * @param key_len length of key + * @param data data to encrypt + * @param data_size size of the data + * @param salt salt value which is used for key derivation + * @param[out] res ciphertext output + * @param[out] res_size size of the ciphertext + */ +static void +encrypt (const struct NonceP *nonce, + const void *key, + size_t key_len, + const void *data, + size_t data_size, + const char *salt, + void **res, + size_t *res_size) +{ + size_t ciphertext_size; + struct SymKeyP skey; + + derive_key (key, + key_len, + nonce, + salt, + &skey); + ciphertext_size = crypto_secretbox_NONCEBYTES + + crypto_secretbox_MACBYTES + data_size; + *res_size = ciphertext_size; + *res = GNUNET_malloc (ciphertext_size); + memcpy (*res, nonce, crypto_secretbox_NONCEBYTES); + GNUNET_assert (0 == + crypto_secretbox_easy (*res + crypto_secretbox_NONCEBYTES, + data, + data_size, + (void *) nonce, + (void *) &skey)); +} + + +/** + * Decryption of data like encrypted recovery document etc. + * + * @param key key which is used to derive a key/iv pair from + * @param key_len length of key + * @param data data to decrypt + * @param data_size size of the data + * @param salt salt value which is used for key derivation + * @param[out] res plaintext output + * @param[out] res_size size of the plaintext + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +decrypt (const void *key, + size_t key_len, + const void *data, + size_t data_size, + const char *salt, + void **res, + size_t *res_size) +{ + const struct NonceP *nonce; + struct SymKeyP skey; + size_t plaintext_size; + + if (data_size < crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + nonce = data; + derive_key (key, + key_len, + nonce, + salt, + &skey); + plaintext_size = data_size - (crypto_secretbox_NONCEBYTES + + crypto_secretbox_MACBYTES); + *res = GNUNET_malloc (plaintext_size); + *res_size = plaintext_size; + if (0 != crypto_secretbox_open_easy (*res, + data + crypto_secretbox_NONCEBYTES, + data_size - crypto_secretbox_NONCEBYTES, + (void *) nonce, + (void *) &skey)) + { + GNUNET_break (0); + GNUNET_free (*res); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Header for encrypted contracts. + */ +struct ContractHeader +{ + /** + * Type of the contract, in NBO. + */ + uint32_t ctype; + + /** + * Length of the encrypted contract, in NBO. + */ + uint32_t clen; + + /** + * Included key material, depending on @e ctype. + */ + union + { + struct TALER_PurseMergePrivateKeyP merge_priv; + } keys; + +}; + + +/** + * Salt we use when encrypting contracts for merge. + */ +#define MERGE_SALT "p2p-merge-contract" void @@ -29,9 +214,52 @@ TALER_CRYPTO_contract_encrypt_for_merge ( const struct TALER_PurseMergePrivateKeyP *merge_priv, const json_t *contract_terms, void **econtract, - size_t *econtract_size) { + struct GNUNET_HashCode key; + char *cstr; + size_t clen; + void *xbuf; + struct ContractHeader *hdr; + struct NonceP nonce; + uLongf cbuf_size; + int ret; + + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv, + &purse_pub->eddsa_pub, + &key)); + cstr = json_dumps (contract_terms, + JSON_COMPACT | JSON_SORT_KEYS); + clen = strlen (cstr); + cbuf_size = compressBound (clen); + xbuf = GNUNET_malloc (cbuf_size); + ret = compress (xbuf, + &cbuf_size, + (const Bytef *) cstr, + clen); + GNUNET_assert (Z_OK == ret); + free (cstr); + hdr = GNUNET_malloc (sizeof (*hdr) + cbuf_size); + hdr->ctype = htonl (TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER); + hdr->clen = htonl ((uint32_t) clen); + hdr->keys.merge_priv = *merge_priv; + memcpy (&hdr[1], + xbuf, + cbuf_size); + GNUNET_free (xbuf); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &nonce, + sizeof (nonce)); + encrypt (&nonce, + &key, + sizeof (key), + hdr, + sizeof (*hdr) + cbuf_size, + MERGE_SALT, + econtract, + econtract_size); + GNUNET_free (hdr); } @@ -43,5 +271,73 @@ TALER_CRYPTO_contract_decrypt_for_merge ( size_t econtract_size, struct TALER_PurseMergePrivateKeyP *merge_priv) { - return NULL; + struct GNUNET_HashCode key; + void *xhdr; + size_t hdr_size; + const struct ContractHeader *hdr; + char *cstr; + uLongf clen; + json_error_t json_error; + json_t *ret; + + if (GNUNET_OK != + GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv, + &purse_pub->eddsa_pub, + &key)) + { + GNUNET_break (0); + return NULL; + } + if (GNUNET_OK != + decrypt (&key, + sizeof (key), + econtract, + econtract_size, + MERGE_SALT, + &xhdr, + &hdr_size)) + { + GNUNET_break_op (0); + return NULL; + } + if (hdr_size < sizeof (*hdr)) + { + GNUNET_break_op (0); + GNUNET_free (xhdr); + return NULL; + } + hdr = xhdr; + clen = ntohl (hdr->clen); + if (clen >= GNUNET_MAX_MALLOC_CHECKED) + { + GNUNET_break_op (0); + GNUNET_free (xhdr); + return NULL; + } + cstr = GNUNET_malloc (clen + 1); + if (Z_OK != + uncompress ((Bytef *) cstr, + &clen, + (const Bytef *) &hdr[1], + hdr_size - sizeof (*hdr))) + { + GNUNET_break_op (0); + GNUNET_free (cstr); + GNUNET_free (xhdr); + return NULL; + } + *merge_priv = hdr->keys.merge_priv; + GNUNET_free (xhdr); + ret = json_loadb ((char *) cstr, + clen, + JSON_DECODE_ANY, + &json_error); + if (NULL == ret) + { + GNUNET_break_op (0); + GNUNET_free (cstr); + return NULL; + } + GNUNET_free (cstr); + return ret; }