662 lines
17 KiB
C
662 lines
17 KiB
C
/*
|
|
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;
|
|
}
|