diff options
Diffstat (limited to 'src/util/crypto_contract.c')
-rw-r--r-- | src/util/crypto_contract.c | 661 |
1 files changed, 661 insertions, 0 deletions
diff --git a/src/util/crypto_contract.c b/src/util/crypto_contract.c new file mode 100644 index 000000000..bec34c983 --- /dev/null +++ b/src/util/crypto_contract.c @@ -0,0 +1,661 @@ +/* + This file is part of TALER + Copyright (C) 2022 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file util/crypto_contract.c + * @brief functions for encrypting and decrypting contracts for P2P payments + * @author Christian Grothoff <christian@grothoff.org> + */ +#include "platform.h" +#include "taler_util.h" +#include <zlib.h> +#include "taler_exchange_service.h" + + +/** + * Different types of contracts supported. + */ +enum ContractFormats +{ + /** + * The encrypted contract represents a payment offer. The receiver + * can merge it into a reserve/account to accept the contract and + * obtain the payment. + */ + TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER = 0, + + /** + * The encrypted contract represents a payment request. + */ + TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST = 1 +}; + + +/** + * 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 +blob_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); + GNUNET_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 +blob_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 ContractHeaderP +{ + /** + * Type of the contract, in NBO. + */ + uint32_t ctype; + + /** + * Length of the encrypted contract, in NBO. + */ + uint32_t clen; +}; + + +/** + * Header for encrypted contracts. + */ +struct ContractHeaderMergeP +{ + /** + * Generic header. + */ + struct ContractHeaderP header; + + /** + * Private key with the merge capability. + */ + struct TALER_PurseMergePrivateKeyP merge_priv; +}; + + +/** + * Salt we use when encrypting contracts for merge. + */ +#define MERGE_SALT "p2p-merge-contract" + + +void +TALER_CRYPTO_contract_encrypt_for_merge ( + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_ContractDiffiePrivateP *contract_priv, + 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 ContractHeaderMergeP *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->header.ctype = htonl (TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER); + hdr->header.clen = htonl ((uint32_t) clen); + hdr->merge_priv = *merge_priv; + GNUNET_memcpy (&hdr[1], + xbuf, + cbuf_size); + GNUNET_free (xbuf); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &nonce, + sizeof (nonce)); + blob_encrypt (&nonce, + &key, + sizeof (key), + hdr, + sizeof (*hdr) + cbuf_size, + MERGE_SALT, + econtract, + econtract_size); + GNUNET_free (hdr); +} + + +json_t * +TALER_CRYPTO_contract_decrypt_for_merge ( + const struct TALER_ContractDiffiePrivateP *contract_priv, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const void *econtract, + size_t econtract_size, + struct TALER_PurseMergePrivateKeyP *merge_priv) +{ + struct GNUNET_HashCode key; + void *xhdr; + size_t hdr_size; + const struct ContractHeaderMergeP *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 != + blob_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; + if (TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER != ntohl (hdr->header.ctype)) + { + GNUNET_break_op (0); + GNUNET_free (xhdr); + return NULL; + } + clen = ntohl (hdr->header.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->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; +} + + +/** + * Salt we use when encrypting contracts for merge. + */ +#define DEPOSIT_SALT "p2p-deposit-contract" + + +void +TALER_CRYPTO_contract_encrypt_for_deposit ( + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_ContractDiffiePrivateP *contract_priv, + const json_t *contract_terms, + void **econtract, + size_t *econtract_size) +{ + struct GNUNET_HashCode key; + char *cstr; + size_t clen; + void *xbuf; + struct ContractHeaderP *hdr; + struct NonceP nonce; + uLongf cbuf_size; + int ret; + void *xecontract; + size_t xecontract_size; + + 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); + GNUNET_assert (NULL != cstr); + 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_REQUEST); + hdr->clen = htonl ((uint32_t) clen); + GNUNET_memcpy (&hdr[1], + xbuf, + cbuf_size); + GNUNET_free (xbuf); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &nonce, + sizeof (nonce)); + blob_encrypt (&nonce, + &key, + sizeof (key), + hdr, + sizeof (*hdr) + cbuf_size, + DEPOSIT_SALT, + &xecontract, + &xecontract_size); + GNUNET_free (hdr); + /* prepend purse_pub */ + *econtract = GNUNET_malloc (xecontract_size + sizeof (*purse_pub)); + GNUNET_memcpy (*econtract, + purse_pub, + sizeof (*purse_pub)); + GNUNET_memcpy (sizeof (*purse_pub) + *econtract, + xecontract, + xecontract_size); + *econtract_size = xecontract_size + sizeof (*purse_pub); + GNUNET_free (xecontract); +} + + +json_t * +TALER_CRYPTO_contract_decrypt_for_deposit ( + const struct TALER_ContractDiffiePrivateP *contract_priv, + const void *econtract, + size_t econtract_size) +{ + const struct TALER_PurseContractPublicKeyP *purse_pub = econtract; + + if (econtract_size < sizeof (*purse_pub)) + { + GNUNET_break_op (0); + return NULL; + } + struct GNUNET_HashCode key; + void *xhdr; + size_t hdr_size; + const struct ContractHeaderP *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; + } + econtract += sizeof (*purse_pub); + econtract_size -= sizeof (*purse_pub); + if (GNUNET_OK != + blob_decrypt (&key, + sizeof (key), + econtract, + econtract_size, + DEPOSIT_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; + if (TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST != ntohl (hdr->ctype)) + { + GNUNET_break_op (0); + GNUNET_free (xhdr); + return NULL; + } + 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; + } + 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; +} + + +/** + * Salt we use when encrypting KYC attributes. + */ +#define ATTRIBUTE_SALT "kyc-attributes" + + +void +TALER_CRYPTO_kyc_attributes_encrypt ( + const struct TALER_AttributeEncryptionKeyP *key, + const json_t *attr, + void **enc_attr, + size_t *enc_attr_size) +{ + uLongf cbuf_size; + char *cstr; + uLongf clen; + void *xbuf; + int ret; + uint32_t belen; + struct NonceP nonce; + + cstr = json_dumps (attr, + JSON_COMPACT | JSON_SORT_KEYS); + GNUNET_assert (NULL != cstr); + clen = strlen (cstr); + GNUNET_assert (clen <= UINT32_MAX); + cbuf_size = compressBound (clen); + xbuf = GNUNET_malloc (cbuf_size + sizeof (uint32_t)); + belen = htonl ((uint32_t) clen); + GNUNET_memcpy (xbuf, + &belen, + sizeof (belen)); + ret = compress (xbuf + 4, + &cbuf_size, + (const Bytef *) cstr, + clen); + GNUNET_assert (Z_OK == ret); + free (cstr); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &nonce, + sizeof (nonce)); + blob_encrypt (&nonce, + key, + sizeof (*key), + xbuf, + cbuf_size + sizeof (uint32_t), + ATTRIBUTE_SALT, + enc_attr, + enc_attr_size); + GNUNET_free (xbuf); +} + + +json_t * +TALER_CRYPTO_kyc_attributes_decrypt ( + const struct TALER_AttributeEncryptionKeyP *key, + const void *enc_attr, + size_t enc_attr_size) +{ + void *xhdr; + size_t hdr_size; + char *cstr; + uLongf clen; + json_error_t json_error; + json_t *ret; + uint32_t belen; + + if (GNUNET_OK != + blob_decrypt (key, + sizeof (*key), + enc_attr, + enc_attr_size, + ATTRIBUTE_SALT, + &xhdr, + &hdr_size)) + { + GNUNET_break_op (0); + return NULL; + } + GNUNET_memcpy (&belen, + xhdr, + sizeof (belen)); + clen = ntohl (belen); + 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 *) (xhdr + sizeof (uint32_t)), + hdr_size - sizeof (uint32_t))) + { + GNUNET_break_op (0); + GNUNET_free (cstr); + GNUNET_free (xhdr); + return NULL; + } + 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; +} |