/*
This file is part of TALER
Copyright (C) 2014-2023 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 lib/exchange_api_handle.c
* @brief Implementation of the "handle" component of the exchange's HTTP API
* @author Sree Harsha Totakura
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include
#include "taler_json_lib.h"
#include "taler_exchange_service.h"
#include "taler_auditor_service.h"
#include "taler_signatures.h"
#include "taler_extensions.h"
#include "exchange_api_handle.h"
#include "exchange_api_curl_defaults.h"
#include "backoff.h"
#include "taler_curl_lib.h"
/**
* Which version of the Taler protocol is implemented
* by this library? Used to determine compatibility.
*/
#define EXCHANGE_PROTOCOL_CURRENT 16
/**
* How many versions are we backwards compatible with?
*/
#define EXCHANGE_PROTOCOL_AGE 0
/**
* Set to 1 for extra debug logging.
*/
#define DEBUG 0
/**
* Current version for (local) JSON serialization of persisted
* /keys data.
*/
#define EXCHANGE_SERIALIZATION_FORMAT_VERSION 0
/**
* How far off do we allow key liftimes to be?
*/
#define LIFETIME_TOLERANCE GNUNET_TIME_UNIT_HOURS
/**
* If the "Expire" cache control header is missing, for
* how long do we assume the reply to be valid at least?
*/
#define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS
/**
* If the "Expire" cache control header is missing, for
* how long do we assume the reply to be valid at least?
*/
#define MINIMUM_EXPIRATION GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_MINUTES, 2)
/**
* Handle for a GET /keys request.
*/
struct TALER_EXCHANGE_GetKeysHandle
{
/**
* The exchange base URL (i.e. "http://exchange.taler.net/")
*/
char *exchange_url;
/**
* The url for the /keys request.
*/
char *url;
/**
* Previous /keys response, NULL for none.
*/
struct TALER_EXCHANGE_Keys *prev_keys;
/**
* Entry for this request with the `struct GNUNET_CURL_Context`.
*/
struct GNUNET_CURL_Job *job;
/**
* Expiration time according to "Expire:" header.
* 0 if not provided by the server.
*/
struct GNUNET_TIME_Timestamp expire;
/**
* Function to call with the exchange's certification data,
* NULL if this has already been done.
*/
TALER_EXCHANGE_GetKeysCallback cert_cb;
/**
* Closure to pass to @e cert_cb.
*/
void *cert_cb_cls;
};
/**
* Frees @a wfm array.
*
* @param wfm fee array to release
* @param wfm_len length of the @a wfm array
*/
static void
free_fees (struct TALER_EXCHANGE_WireFeesByMethod *wfm,
unsigned int wfm_len)
{
for (unsigned int i = 0; ifees_head)
{
struct TALER_EXCHANGE_WireAggregateFees *fe
= wfmi->fees_head;
wfmi->fees_head = fe->next;
GNUNET_free (fe);
}
GNUNET_free (wfmi->method);
}
GNUNET_free (wfm);
}
/**
* Parse wire @a fees and return array.
*
* @param master_pub master public key to use to check signatures
* @param currency currency amounts are expected in
* @param fees json AggregateTransferFee to parse
* @param[out] fees_len set to length of returned array
* @return NULL on error
*/
static struct TALER_EXCHANGE_WireFeesByMethod *
parse_fees (const struct TALER_MasterPublicKeyP *master_pub,
const char *currency,
const json_t *fees,
unsigned int *fees_len)
{
struct TALER_EXCHANGE_WireFeesByMethod *fbm;
unsigned int fbml = json_object_size (fees);
unsigned int i = 0;
const char *key;
const json_t *fee_array;
fbm = GNUNET_new_array (fbml,
struct TALER_EXCHANGE_WireFeesByMethod);
*fees_len = fbml;
json_object_foreach ((json_t *) fees, key, fee_array) {
struct TALER_EXCHANGE_WireFeesByMethod *fe = &fbm[i++];
unsigned int idx;
json_t *fee;
fe->method = GNUNET_strdup (key);
fe->fees_head = NULL;
json_array_foreach (fee_array, idx, fee)
{
struct TALER_EXCHANGE_WireAggregateFees *wa
= GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees);
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("sig",
&wa->master_sig),
TALER_JSON_spec_amount ("wire_fee",
currency,
&wa->fees.wire),
TALER_JSON_spec_amount ("closing_fee",
currency,
&wa->fees.closing),
GNUNET_JSON_spec_timestamp ("start_date",
&wa->start_date),
GNUNET_JSON_spec_timestamp ("end_date",
&wa->end_date),
GNUNET_JSON_spec_end ()
};
wa->next = fe->fees_head;
fe->fees_head = wa;
if (GNUNET_OK !=
GNUNET_JSON_parse (fee,
spec,
NULL,
NULL))
{
GNUNET_break_op (0);
free_fees (fbm,
i);
return NULL;
}
if (GNUNET_OK !=
TALER_exchange_offline_wire_fee_verify (
key,
wa->start_date,
wa->end_date,
&wa->fees,
master_pub,
&wa->master_sig))
{
GNUNET_break_op (0);
free_fees (fbm,
i);
return NULL;
}
} /* for all fees over time */
} /* for all methods */
GNUNET_assert (i == fbml);
return fbm;
}
void
TEAH_get_auditors_for_dc (
struct TALER_EXCHANGE_Keys *keys,
TEAH_AuditorCallback ac,
void *ac_cls)
{
if (0 == keys->num_auditors)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"No auditor available. Not submitting deposit confirmations.\n");
return;
}
for (unsigned int i = 0; inum_auditors; i++)
{
const struct TALER_EXCHANGE_AuditorInformation *auditor
= &keys->auditors[i];
ac (ac_cls,
auditor->auditor_url,
&auditor->auditor_pub);
}
}
#define EXITIF(cond) \
do { \
if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
} while (0)
/**
* Parse a exchange's signing key encoded in JSON.
*
* @param[out] sign_key where to return the result
* @param check_sigs should we check signatures?
* @param sign_key_obj json to parse
* @param master_key master key to use to verify signature
* @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
* invalid or the @a sign_key_obj is malformed.
*/
static enum GNUNET_GenericReturnValue
parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key,
bool check_sigs,
const json_t *sign_key_obj,
const struct TALER_MasterPublicKeyP *master_key)
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("master_sig",
&sign_key->master_sig),
GNUNET_JSON_spec_fixed_auto ("key",
&sign_key->key),
GNUNET_JSON_spec_timestamp ("stamp_start",
&sign_key->valid_from),
GNUNET_JSON_spec_timestamp ("stamp_expire",
&sign_key->valid_until),
GNUNET_JSON_spec_timestamp ("stamp_end",
&sign_key->valid_legal),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (sign_key_obj,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (! check_sigs)
return GNUNET_OK;
if (GNUNET_OK !=
TALER_exchange_offline_signkey_validity_verify (
&sign_key->key,
sign_key->valid_from,
sign_key->valid_until,
sign_key->valid_legal,
master_key,
&sign_key->master_sig))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Parse a exchange's denomination key encoded in JSON partially.
*
* Only the values for master_sig, timestamps and the cipher-specific public
* key are parsed. All other fields (fees, age_mask, value) MUST have been set
* prior to calling this function, otherwise the signature verification
* performed within this function will fail.
*
* @param[out] denom_key where to return the result
* @param cipher cipher type to parse
* @param check_sigs should we check signatures?
* @param denom_key_obj json to parse
* @param master_key master key to use to verify signature
* @param[in,out] hash_xor where to accumulate data for signature verification via XOR
* @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
* invalid or the json malformed.
*/
static enum GNUNET_GenericReturnValue
parse_json_denomkey_partially (
struct TALER_EXCHANGE_DenomPublicKey *denom_key,
enum TALER_DenominationCipher cipher,
bool check_sigs,
const json_t *denom_key_obj,
struct TALER_MasterPublicKeyP *master_key,
struct GNUNET_HashCode *hash_xor)
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("master_sig",
&denom_key->master_sig),
GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
&denom_key->expire_deposit),
GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
&denom_key->withdraw_valid_until),
GNUNET_JSON_spec_timestamp ("stamp_start",
&denom_key->valid_from),
GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
&denom_key->expire_legal),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_bool ("lost",
&denom_key->lost),
NULL),
TALER_JSON_spec_denom_pub_cipher (NULL,
cipher,
&denom_key->key),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (denom_key_obj,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
TALER_denom_pub_hash (&denom_key->key,
&denom_key->h_key);
if (NULL != hash_xor)
GNUNET_CRYPTO_hash_xor (&denom_key->h_key.hash,
hash_xor,
hash_xor);
if (! check_sigs)
return GNUNET_OK;
EXITIF (GNUNET_SYSERR ==
TALER_exchange_offline_denom_validity_verify (
&denom_key->h_key,
denom_key->valid_from,
denom_key->withdraw_valid_until,
denom_key->expire_deposit,
denom_key->expire_legal,
&denom_key->value,
&denom_key->fees,
master_key,
&denom_key->master_sig));
return GNUNET_OK;
EXITIF_exit:
/* invalidate denom_key, just to be sure */
memset (denom_key,
0,
sizeof (*denom_key));
GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
/**
* Parse a exchange's auditor information encoded in JSON.
*
* @param[out] auditor where to return the result
* @param check_sigs should we check signatures
* @param auditor_obj json to parse
* @param key_data information about denomination keys
* @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
* invalid or the json malformed.
*/
static enum GNUNET_GenericReturnValue
parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
bool check_sigs,
const json_t *auditor_obj,
const struct TALER_EXCHANGE_Keys *key_data)
{
const json_t *keys;
json_t *key;
unsigned int off;
unsigned int pos;
const char *auditor_url;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("auditor_pub",
&auditor->auditor_pub),
GNUNET_JSON_spec_string ("auditor_url",
&auditor_url),
GNUNET_JSON_spec_array_const ("denomination_keys",
&keys),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (auditor_obj,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
#if DEBUG
json_dumpf (auditor_obj,
stderr,
JSON_INDENT (2));
#endif
return GNUNET_SYSERR;
}
auditor->auditor_url = GNUNET_strdup (auditor_url);
auditor->denom_keys
= GNUNET_new_array (json_array_size (keys),
struct TALER_EXCHANGE_AuditorDenominationInfo);
pos = 0;
json_array_foreach (keys, off, key) {
struct TALER_AuditorSignatureP auditor_sig;
struct TALER_DenominationHashP denom_h;
const struct TALER_EXCHANGE_DenomPublicKey *dk = NULL;
unsigned int dk_off = UINT_MAX;
struct GNUNET_JSON_Specification kspec[] = {
GNUNET_JSON_spec_fixed_auto ("auditor_sig",
&auditor_sig),
GNUNET_JSON_spec_fixed_auto ("denom_pub_h",
&denom_h),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (key,
kspec,
NULL, NULL))
{
GNUNET_break_op (0);
continue;
}
for (unsigned int j = 0; jnum_denom_keys; j++)
{
if (0 == GNUNET_memcmp (&denom_h,
&key_data->denom_keys[j].h_key))
{
dk = &key_data->denom_keys[j];
dk_off = j;
break;
}
}
if (NULL == dk)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Auditor signed denomination %s, which we do not know. Ignoring signature.\n",
GNUNET_h2s (&denom_h.hash));
continue;
}
if (check_sigs)
{
if (GNUNET_OK !=
TALER_auditor_denom_validity_verify (
auditor_url,
&dk->h_key,
&key_data->master_pub,
dk->valid_from,
dk->withdraw_valid_until,
dk->expire_deposit,
dk->expire_legal,
&dk->value,
&dk->fees,
&auditor->auditor_pub,
&auditor_sig))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
}
auditor->denom_keys[pos].denom_key_offset = dk_off;
auditor->denom_keys[pos].auditor_sig = auditor_sig;
pos++;
}
auditor->num_denom_keys = pos;
return GNUNET_OK;
}
/**
* Parse a exchange's global fee information encoded in JSON.
*
* @param[out] gf where to return the result
* @param check_sigs should we check signatures
* @param fee_obj json to parse
* @param key_data already parsed information about the exchange
* @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
* invalid or the json malformed.
*/
static enum GNUNET_GenericReturnValue
parse_global_fee (struct TALER_EXCHANGE_GlobalFee *gf,
bool check_sigs,
const json_t *fee_obj,
const struct TALER_EXCHANGE_Keys *key_data)
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_timestamp ("start_date",
&gf->start_date),
GNUNET_JSON_spec_timestamp ("end_date",
&gf->end_date),
GNUNET_JSON_spec_relative_time ("purse_timeout",
&gf->purse_timeout),
GNUNET_JSON_spec_relative_time ("history_expiration",
&gf->history_expiration),
GNUNET_JSON_spec_uint32 ("purse_account_limit",
&gf->purse_account_limit),
TALER_JSON_SPEC_GLOBAL_FEES (key_data->currency,
&gf->fees),
GNUNET_JSON_spec_fixed_auto ("master_sig",
&gf->master_sig),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (fee_obj,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
#if DEBUG
json_dumpf (fee_obj,
stderr,
JSON_INDENT (2));
#endif
return GNUNET_SYSERR;
}
if (check_sigs)
{
if (GNUNET_OK !=
TALER_exchange_offline_global_fee_verify (
gf->start_date,
gf->end_date,
&gf->fees,
gf->purse_timeout,
gf->history_expiration,
gf->purse_account_limit,
&key_data->master_pub,
&gf->master_sig))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
}
GNUNET_JSON_parse_free (spec);
return GNUNET_OK;
}
/**
* Compare two denomination keys. Ignores revocation data.
*
* @param denom1 first denomination key
* @param denom2 second denomination key
* @return 0 if the two keys are equal (not necessarily
* the same object), 1 otherwise.
*/
static unsigned int
denoms_cmp (const struct TALER_EXCHANGE_DenomPublicKey *denom1,
const struct TALER_EXCHANGE_DenomPublicKey *denom2)
{
struct TALER_EXCHANGE_DenomPublicKey tmp1;
struct TALER_EXCHANGE_DenomPublicKey tmp2;
if (0 !=
TALER_denom_pub_cmp (&denom1->key,
&denom2->key))
return 1;
tmp1 = *denom1;
tmp2 = *denom2;
tmp1.revoked = false;
tmp2.revoked = false;
memset (&tmp1.key,
0,
sizeof (tmp1.key));
memset (&tmp2.key,
0,
sizeof (tmp2.key));
return GNUNET_memcmp (&tmp1,
&tmp2);
}
/**
* Decode the JSON in @a resp_obj from the /keys response
* and store the data in the @a key_data.
*
* @param[in] resp_obj JSON object to parse
* @param check_sig true if we should check the signature
* @param[out] key_data where to store the results we decoded
* @param[out] vc where to store version compatibility data
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
* (malformed JSON)
*/
static enum GNUNET_GenericReturnValue
decode_keys_json (const json_t *resp_obj,
bool check_sig,
struct TALER_EXCHANGE_Keys *key_data,
enum TALER_EXCHANGE_VersionCompatibility *vc)
{
struct TALER_ExchangeSignatureP denominations_sig;
struct GNUNET_HashCode hash_xor = {0};
struct TALER_ExchangePublicKeyP pub;
const json_t *wblwk = NULL;
const json_t *global_fees;
const json_t *sign_keys_array;
const json_t *denominations_by_group;
const json_t *auditors_array;
const json_t *recoup_array = NULL;
const json_t *manifests = NULL;
bool no_extensions = false;
bool no_signature = false;
const json_t *accounts;
const json_t *fees;
const json_t *wads;
if (JSON_OBJECT != json_typeof (resp_obj))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
#if DEBUG
json_dumpf (resp_obj,
stderr,
JSON_INDENT (2));
#endif
/* check the version first */
{
const char *ver;
unsigned int age;
unsigned int revision;
unsigned int current;
char dummy;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("version",
&ver),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (resp_obj,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (3 != sscanf (ver,
"%u:%u:%u%c",
¤t,
&revision,
&age,
&dummy))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
*vc = TALER_EXCHANGE_VC_MATCH;
if (EXCHANGE_PROTOCOL_CURRENT < current)
{
*vc |= TALER_EXCHANGE_VC_NEWER;
if (EXCHANGE_PROTOCOL_CURRENT < current - age)
*vc |= TALER_EXCHANGE_VC_INCOMPATIBLE;
}
if (EXCHANGE_PROTOCOL_CURRENT > current)
{
*vc |= TALER_EXCHANGE_VC_OLDER;
if (EXCHANGE_PROTOCOL_CURRENT - EXCHANGE_PROTOCOL_AGE > current)
*vc |= TALER_EXCHANGE_VC_INCOMPATIBLE;
}
key_data->version = GNUNET_strdup (ver);
}
{
const char *currency;
const char *asset_type;
struct GNUNET_JSON_Specification mspec[] = {
GNUNET_JSON_spec_fixed_auto (
"denominations_sig",
&denominations_sig),
GNUNET_JSON_spec_fixed_auto (
"eddsa_pub",
&pub),
GNUNET_JSON_spec_fixed_auto (
"master_public_key",
&key_data->master_pub),
GNUNET_JSON_spec_array_const ("accounts",
&accounts),
GNUNET_JSON_spec_object_const ("wire_fees",
&fees),
GNUNET_JSON_spec_array_const ("wads",
&wads),
GNUNET_JSON_spec_timestamp (
"list_issue_date",
&key_data->list_issue_date),
GNUNET_JSON_spec_relative_time (
"reserve_closing_delay",
&key_data->reserve_closing_delay),
GNUNET_JSON_spec_string (
"currency",
¤cy),
GNUNET_JSON_spec_uint32 (
"currency_fraction_digits",
&key_data->currency_fraction_digits),
GNUNET_JSON_spec_string (
"asset_type",
&asset_type),
GNUNET_JSON_spec_array_const (
"global_fees",
&global_fees),
GNUNET_JSON_spec_array_const (
"signkeys",
&sign_keys_array),
GNUNET_JSON_spec_array_const (
"denominations",
&denominations_by_group),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_array_const (
"recoup",
&recoup_array),
NULL),
GNUNET_JSON_spec_array_const (
"auditors",
&auditors_array),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_bool (
"rewards_allowed",
&key_data->rewards_allowed),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_object_const ("extensions",
&manifests),
&no_extensions),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_fixed_auto (
"extensions_sig",
&key_data->extensions_sig),
&no_signature),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_array_const (
"wallet_balance_limit_without_kyc",
&wblwk),
NULL),
GNUNET_JSON_spec_end ()
};
const char *emsg;
unsigned int eline;
if (GNUNET_OK !=
GNUNET_JSON_parse (resp_obj,
(check_sig) ? mspec : &mspec[2],
&emsg,
&eline))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Parsing /keys failed for `%s' (%u)\n",
emsg,
eline);
EXITIF (1);
}
{
struct GNUNET_JSON_Specification sspec[] = {
TALER_JSON_spec_amount (
"stefan_abs",
currency,
&key_data->stefan_abs),
TALER_JSON_spec_amount (
"stefan_log",
currency,
&key_data->stefan_log),
TALER_JSON_spec_amount (
"stefan_lin",
currency,
&key_data->stefan_lin),
GNUNET_JSON_spec_end ()
};
EXITIF (GNUNET_OK !=
GNUNET_JSON_parse (resp_obj,
sspec,
NULL, NULL));
}
key_data->currency = GNUNET_strdup (currency);
key_data->asset_type = GNUNET_strdup (asset_type);
if (! no_extensions)
key_data->extensions = json_incref ((json_t *) manifests);
}
/* parse the global fees */
key_data->num_global_fees
= json_array_size (global_fees);
if (0 != key_data->num_global_fees)
{
json_t *global_fee;
unsigned int index;
key_data->global_fees
= GNUNET_new_array (key_data->num_global_fees,
struct TALER_EXCHANGE_GlobalFee);
json_array_foreach (global_fees, index, global_fee)
{
EXITIF (GNUNET_SYSERR ==
parse_global_fee (&key_data->global_fees[index],
check_sig,
global_fee,
key_data));
}
}
/* parse the signing keys */
key_data->num_sign_keys
= json_array_size (sign_keys_array);
if (0 != key_data->num_sign_keys)
{
json_t *sign_key_obj;
unsigned int index;
key_data->sign_keys
= GNUNET_new_array (key_data->num_sign_keys,
struct TALER_EXCHANGE_SigningPublicKey);
json_array_foreach (sign_keys_array, index, sign_key_obj) {
EXITIF (GNUNET_SYSERR ==
parse_json_signkey (&key_data->sign_keys[index],
check_sig,
sign_key_obj,
&key_data->master_pub));
}
}
/* Parse balance limits */
if (NULL != wblwk)
{
key_data->wblwk_length = json_array_size (wblwk);
key_data->wallet_balance_limit_without_kyc
= GNUNET_new_array (key_data->wblwk_length,
struct TALER_Amount);
for (unsigned int i = 0; iwblwk_length; i++)
{
struct TALER_Amount *a = &key_data->wallet_balance_limit_without_kyc[i];
const json_t *aj = json_array_get (wblwk,
i);
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount (NULL,
key_data->currency,
a),
GNUNET_JSON_spec_end ()
};
EXITIF (GNUNET_OK !=
GNUNET_JSON_parse (aj,
spec,
NULL, NULL));
}
}
/* Parse wire accounts */
key_data->fees = parse_fees (&key_data->master_pub,
key_data->currency,
fees,
&key_data->fees_len);
EXITIF (NULL == key_data->fees);
/* parse accounts */
GNUNET_array_grow (key_data->accounts,
key_data->accounts_len,
json_array_size (accounts));
EXITIF (GNUNET_OK !=
TALER_EXCHANGE_parse_accounts (&key_data->master_pub,
accounts,
key_data->accounts_len,
key_data->accounts));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Parsed %u wire accounts from JSON\n",
(unsigned int) json_array_size (accounts));
/* Parse the supported extension(s): age-restriction. */
/* TODO: maybe lift all this into a FP in TALER_Extension ? */
if (! no_extensions)
{
if (no_signature)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"found extensions without signature\n");
}
else
{
/* We have an extensions object. Verify its signature. */
EXITIF (GNUNET_OK !=
TALER_extensions_verify_manifests_signature (
manifests,
&key_data->extensions_sig,
&key_data->master_pub));
/* Parse and set the the configuration of the extensions accordingly */
EXITIF (GNUNET_OK !=
TALER_extensions_load_manifests (manifests));
}
/* Assuming we might have now a new value for age_mask, set it in key_data */
key_data->age_mask = TALER_extensions_get_age_restriction_mask ();
}
/*
* Parse the denomination keys, merging with the
* possibly EXISTING array as required (/keys cherry picking).
*
* The denominations are grouped by common values of
* {cipher, value, fee, age_mask}.
*/
{
json_t *group_obj;
unsigned int group_idx;
json_array_foreach (denominations_by_group, group_idx, group_obj)
{
/* Running XOR of each SHA512 hash of the denominations' public key in
this group. Used to compare against group.hash after all keys have
been parsed. */
struct GNUNET_HashCode group_hash_xor = {0};
/* First, parse { cipher, fees, value, age_mask, hash } of the current
group. */
struct TALER_DenominationGroup group = {0};
const json_t *denom_keys_array;
struct GNUNET_JSON_Specification group_spec[] = {
TALER_JSON_spec_denomination_group (NULL,
key_data->currency,
&group),
GNUNET_JSON_spec_array_const ("denoms",
&denom_keys_array),
GNUNET_JSON_spec_end ()
};
json_t *denom_key_obj;
unsigned int index;
EXITIF (GNUNET_SYSERR ==
GNUNET_JSON_parse (group_obj,
group_spec,
NULL,
NULL));
/* Now, parse the individual denominations */
json_array_foreach (denom_keys_array, index, denom_key_obj)
{
/* Set the common fields from the group for this particular
denomination. Required to make the validity check inside
parse_json_denomkey_partially pass */
struct TALER_EXCHANGE_DenomPublicKey dk = {
.key.cipher = group.cipher,
.value = group.value,
.fees = group.fees,
.key.age_mask = group.age_mask
};
bool found = false;
EXITIF (GNUNET_SYSERR ==
parse_json_denomkey_partially (&dk,
group.cipher,
check_sig,
denom_key_obj,
&key_data->master_pub,
check_sig ? &hash_xor : NULL));
/* Build the running xor of the SHA512-hash of the public keys for the group */
GNUNET_CRYPTO_hash_xor (&dk.h_key.hash,
&group_hash_xor,
&group_hash_xor);
for (unsigned int j = 0;
jnum_denom_keys;
j++)
{
if (0 == denoms_cmp (&dk,
&key_data->denom_keys[j]))
{
found = true;
break;
}
}
if (found)
{
/* 0:0:0 did not support /keys cherry picking */
TALER_LOG_DEBUG ("Skipping denomination key: already know it\n");
TALER_denom_pub_free (&dk.key);
continue;
}
if (key_data->denom_keys_size == key_data->num_denom_keys)
GNUNET_array_grow (key_data->denom_keys,
key_data->denom_keys_size,
key_data->denom_keys_size * 2 + 2);
key_data->denom_keys[key_data->num_denom_keys++] = dk;
/* Update "last_denom_issue_date" */
TALER_LOG_DEBUG ("Adding denomination key that is valid_until %s\n",
GNUNET_TIME_timestamp2s (dk.valid_from));
key_data->last_denom_issue_date
= GNUNET_TIME_timestamp_max (key_data->last_denom_issue_date,
dk.valid_from);
}; /* end of json_array_foreach over denominations */
/* The calculated group_hash_xor must be the same as group.hash from
the JSON. */
EXITIF (0 !=
GNUNET_CRYPTO_hash_cmp (&group_hash_xor,
&group.hash));
} /* end of json_array_foreach over groups of denominations */
} /* end of scope for group_ojb/group_idx */
/* parse the auditor information */
{
json_t *auditor_info;
unsigned int index;
/* Merge with the existing auditor information we have (/keys cherry picking) */
json_array_foreach (auditors_array, index, auditor_info)
{
struct TALER_EXCHANGE_AuditorInformation ai;
bool found = false;
memset (&ai,
0,
sizeof (ai));
EXITIF (GNUNET_SYSERR ==
parse_json_auditor (&ai,
check_sig,
auditor_info,
key_data));
for (unsigned int j = 0; jnum_auditors; j++)
{
struct TALER_EXCHANGE_AuditorInformation *aix = &key_data->auditors[j];
if (0 == GNUNET_memcmp (&ai.auditor_pub,
&aix->auditor_pub))
{
found = true;
/* Merge denomination key signatures of downloaded /keys into existing
auditor information 'aix'. */
TALER_LOG_DEBUG (
"Merging %u new audited keys with %u known audited keys\n",
aix->num_denom_keys,
ai.num_denom_keys);
for (unsigned int i = 0; inum_denom_keys; k++)
{
if (aix->denom_keys[k].denom_key_offset ==
ai.denom_keys[i].denom_key_offset)
{
kfound = true;
break;
}
}
if (! kfound)
GNUNET_array_append (aix->denom_keys,
aix->num_denom_keys,
ai.denom_keys[i]);
}
break;
}
}
if (found)
{
GNUNET_array_grow (ai.denom_keys,
ai.num_denom_keys,
0);
GNUNET_free (ai.auditor_url);
continue; /* we are done */
}
if (key_data->auditors_size == key_data->num_auditors)
GNUNET_array_grow (key_data->auditors,
key_data->auditors_size,
key_data->auditors_size * 2 + 2);
GNUNET_assert (NULL != ai.auditor_url);
key_data->auditors[key_data->num_auditors++] = ai;
};
}
/* parse the revocation/recoup information */
if (NULL != recoup_array)
{
json_t *recoup_info;
unsigned int index;
json_array_foreach (recoup_array, index, recoup_info)
{
struct TALER_DenominationHashP h_denom_pub;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
&h_denom_pub),
GNUNET_JSON_spec_end ()
};
EXITIF (GNUNET_OK !=
GNUNET_JSON_parse (recoup_info,
spec,
NULL, NULL));
for (unsigned int j = 0;
jnum_denom_keys;
j++)
{
if (0 == GNUNET_memcmp (&h_denom_pub,
&key_data->denom_keys[j].h_key))
{
key_data->denom_keys[j].revoked = true;
break;
}
}
}
}
if (check_sig)
{
EXITIF (GNUNET_OK !=
TALER_EXCHANGE_test_signing_key (key_data,
&pub));
EXITIF (GNUNET_OK !=
TALER_exchange_online_key_set_verify (
key_data->list_issue_date,
&hash_xor,
&pub,
&denominations_sig));
}
return GNUNET_OK;
EXITIF_exit:
*vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR;
return GNUNET_SYSERR;
}
/**
* Callback used when downloading the reply to a /keys request
* is complete.
*
* @param cls the `struct KeysRequest`
* @param response_code HTTP response code, 0 on error
* @param resp_obj parsed JSON result, NULL on error
*/
static void
keys_completed_cb (void *cls,
long response_code,
const void *resp_obj)
{
struct TALER_EXCHANGE_GetKeysHandle *gkh = cls;
const json_t *j = resp_obj;
struct TALER_EXCHANGE_Keys *kd = NULL;
struct TALER_EXCHANGE_KeysResponse kresp = {
.hr.reply = j,
.hr.http_status = (unsigned int) response_code,
.details.ok.compat = TALER_EXCHANGE_VC_PROTOCOL_ERROR,
};
gkh->job = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Received keys from URL `%s' with status %ld and expiration %s.\n",
gkh->url,
response_code,
GNUNET_TIME_timestamp2s (gkh->expire));
if (GNUNET_TIME_absolute_is_past (gkh->expire.abs_time))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Exchange failed to give expiration time, assuming in %s\n",
GNUNET_TIME_relative2s (DEFAULT_EXPIRATION,
true));
gkh->expire
= GNUNET_TIME_absolute_to_timestamp (
GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION));
}
switch (response_code)
{
case 0:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to receive /keys response from exchange %s\n",
gkh->exchange_url);
break;
case MHD_HTTP_OK:
if (NULL == j)
{
GNUNET_break (0);
response_code = 0;
break;
}
kd = GNUNET_new (struct TALER_EXCHANGE_Keys);
kd->exchange_url = GNUNET_strdup (gkh->exchange_url);
if (NULL != gkh->prev_keys)
{
const struct TALER_EXCHANGE_Keys *kd_old = gkh->prev_keys;
/* We keep the denomination keys and auditor signatures from the
previous iteration (/keys cherry picking) */
kd->num_denom_keys
= kd_old->num_denom_keys;
kd->last_denom_issue_date
= kd_old->last_denom_issue_date;
GNUNET_array_grow (kd->denom_keys,
kd->denom_keys_size,
kd->num_denom_keys);
/* First make a shallow copy, we then need another pass for the RSA key... */
GNUNET_memcpy (kd->denom_keys,
kd_old->denom_keys,
kd_old->num_denom_keys
* sizeof (struct TALER_EXCHANGE_DenomPublicKey));
for (unsigned int i = 0; inum_denom_keys; i++)
TALER_denom_pub_deep_copy (&kd->denom_keys[i].key,
&kd_old->denom_keys[i].key);
kd->num_auditors = kd_old->num_auditors;
kd->auditors = GNUNET_new_array (kd->num_auditors,
struct TALER_EXCHANGE_AuditorInformation);
/* Now the necessary deep copy... */
for (unsigned int i = 0; inum_auditors; i++)
{
const struct TALER_EXCHANGE_AuditorInformation *aold =
&kd_old->auditors[i];
struct TALER_EXCHANGE_AuditorInformation *anew = &kd->auditors[i];
anew->auditor_pub = aold->auditor_pub;
anew->auditor_url = GNUNET_strdup (aold->auditor_url);
GNUNET_array_grow (anew->denom_keys,
anew->num_denom_keys,
aold->num_denom_keys);
GNUNET_memcpy (
anew->denom_keys,
aold->denom_keys,
aold->num_denom_keys
* sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo));
}
}
/* Now decode fresh /keys response */
if (GNUNET_OK !=
decode_keys_json (j,
true,
kd,
&kresp.details.ok.compat))
{
TALER_LOG_ERROR ("Could not decode /keys response\n");
kd->rc = 1;
TALER_EXCHANGE_keys_decref (kd);
kd = NULL;
kresp.hr.http_status = 0;
kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
kd->rc = 1;
kd->key_data_expiration = gkh->expire;
if (GNUNET_TIME_relative_cmp (
GNUNET_TIME_absolute_get_remaining (gkh->expire.abs_time),
<,
MINIMUM_EXPIRATION))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Exchange returned keys with expiration time below %s. Compensating.\n",
GNUNET_TIME_relative2s (MINIMUM_EXPIRATION,
true));
kd->key_data_expiration
= GNUNET_TIME_relative_to_timestamp (MINIMUM_EXPIRATION);
}
kresp.details.ok.keys = kd;
break;
case MHD_HTTP_BAD_REQUEST:
case MHD_HTTP_UNAUTHORIZED:
case MHD_HTTP_FORBIDDEN:
case MHD_HTTP_NOT_FOUND:
if (NULL == j)
{
kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
}
else
{
kresp.hr.ec = TALER_JSON_get_error_code (j);
kresp.hr.hint = TALER_JSON_get_error_hint (j);
}
break;
default:
if (NULL == j)
{
kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
}
else
{
kresp.hr.ec = TALER_JSON_get_error_code (j);
kresp.hr.hint = TALER_JSON_get_error_hint (j);
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
(int) kresp.hr.ec);
break;
}
gkh->cert_cb (gkh->cert_cb_cls,
&kresp,
kd);
TALER_EXCHANGE_get_keys_cancel (gkh);
}
/**
* Define a max length for the HTTP "Expire:" header
*/
#define MAX_DATE_LINE_LEN 32
/**
* Parse HTTP timestamp.
*
* @param dateline header to parse header
* @param[out] at where to write the result
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
parse_date_string (const char *dateline,
struct GNUNET_TIME_Timestamp *at)
{
static const char *MONTHS[] =
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL };
int year;
int mon;
int day;
int hour;
int min;
int sec;
char month[4];
struct tm tm;
time_t t;
/* We recognize the three formats in RFC2616, section 3.3.1. Month
names are always in English. The formats are:
Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
Note that the first is preferred.
*/
if (strlen (dateline) > MAX_DATE_LINE_LEN)
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
while (*dateline == ' ')
++dateline;
while (*dateline && *dateline != ' ')
++dateline;
while (*dateline == ' ')
++dateline;
/* We just skipped over the day of the week. Now we have:*/
if ( (sscanf (dateline,
"%d %3s %d %d:%d:%d",
&day, month, &year, &hour, &min, &sec) != 6) &&
(sscanf (dateline,
"%d-%3s-%d %d:%d:%d",
&day, month, &year, &hour, &min, &sec) != 6) &&
(sscanf (dateline,
"%3s %d %d:%d:%d %d",
month, &day, &hour, &min, &sec, &year) != 6) )
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
/* Two digit dates are defined to be relative to 1900; all other dates
* are supposed to be represented as four digits. */
if (year < 100)
year += 1900;
for (mon = 0; ; mon++)
{
if (! MONTHS[mon])
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if (0 == strcasecmp (month,
MONTHS[mon]))
break;
}
memset (&tm, 0, sizeof(tm));
tm.tm_year = year - 1900;
tm.tm_mon = mon;
tm.tm_mday = day;
tm.tm_hour = hour;
tm.tm_min = min;
tm.tm_sec = sec;
t = mktime (&tm);
if (((time_t) -1) == t)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"mktime");
return GNUNET_SYSERR;
}
if (t < 0)
t = 0; /* can happen due to timezone issues if date was 1.1.1970 */
*at = GNUNET_TIME_timestamp_from_s (t);
return GNUNET_OK;
}
/**
* Function called for each header in the HTTP /keys response.
* Finds the "Expire:" header and parses it, storing the result
* in the "expire" field of the keys request.
*
* @param buffer header data received
* @param size size of an item in @a buffer
* @param nitems number of items in @a buffer
* @param userdata the `struct TALER_EXCHANGE_GetKeysHandle`
* @return `size * nitems` on success (everything else aborts)
*/
static size_t
header_cb (char *buffer,
size_t size,
size_t nitems,
void *userdata)
{
struct TALER_EXCHANGE_GetKeysHandle *kr = userdata;
size_t total = size * nitems;
char *val;
if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": "))
return total;
if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ",
buffer,
strlen (MHD_HTTP_HEADER_EXPIRES ": ")))
return total;
val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")],
total - strlen (MHD_HTTP_HEADER_EXPIRES ": "));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Found %s header `%s'\n",
MHD_HTTP_HEADER_EXPIRES,
val);
if (GNUNET_OK !=
parse_date_string (val,
&kr->expire))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to parse %s-header `%s'\n",
MHD_HTTP_HEADER_EXPIRES,
val);
kr->expire = GNUNET_TIME_UNIT_ZERO_TS;
}
GNUNET_free (val);
return total;
}
struct TALER_EXCHANGE_GetKeysHandle *
TALER_EXCHANGE_get_keys (
struct GNUNET_CURL_Context *ctx,
const char *url,
struct TALER_EXCHANGE_Keys *last_keys,
TALER_EXCHANGE_GetKeysCallback cert_cb,
void *cert_cb_cls)
{
struct TALER_EXCHANGE_GetKeysHandle *gkh;
CURL *eh;
char last_date[80] = { 0 };
TALER_LOG_DEBUG ("Connecting to the exchange (%s)\n",
url);
gkh = GNUNET_new (struct TALER_EXCHANGE_GetKeysHandle);
gkh->exchange_url = GNUNET_strdup (url);
gkh->cert_cb = cert_cb;
gkh->cert_cb_cls = cert_cb_cls;
if (NULL != last_keys)
{
gkh->prev_keys = TALER_EXCHANGE_keys_incref (last_keys);
TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n",
GNUNET_TIME_timestamp2s (
last_keys->last_denom_issue_date));
GNUNET_snprintf (last_date,
sizeof (last_date),
"%llu",
(unsigned long long)
last_keys->last_denom_issue_date.abs_time.abs_value_us
/ 1000000LLU);
}
gkh->url = TALER_url_join (url,
"keys",
(NULL != last_keys)
? "last_issue_date"
: NULL,
(NULL != last_keys)
? last_date
: NULL,
NULL);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Requesting keys with URL `%s'.\n",
gkh->url);
eh = TALER_EXCHANGE_curl_easy_get_ (gkh->url);
if (NULL == eh)
{
GNUNET_break (0);
GNUNET_free (gkh->exchange_url);
GNUNET_free (gkh->url);
GNUNET_free (gkh);
return NULL;
}
GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_VERBOSE,
0));
GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_TIMEOUT,
120 /* seconds */));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_HEADERFUNCTION,
&header_cb));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_HEADERDATA,
gkh));
gkh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
eh,
&keys_completed_cb,
gkh);
return gkh;
}
void
TALER_EXCHANGE_get_keys_cancel (
struct TALER_EXCHANGE_GetKeysHandle *gkh)
{
if (NULL != gkh->job)
{
GNUNET_CURL_job_cancel (gkh->job);
gkh->job = NULL;
}
TALER_EXCHANGE_keys_decref (gkh->prev_keys);
GNUNET_free (gkh->exchange_url);
GNUNET_free (gkh->url);
GNUNET_free (gkh);
}
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_test_signing_key (
const struct TALER_EXCHANGE_Keys *keys,
const struct TALER_ExchangePublicKeyP *pub)
{
struct GNUNET_TIME_Absolute now;
/* we will check using a tolerance of 1h for the time */
now = GNUNET_TIME_absolute_get ();
for (unsigned int i = 0; inum_sign_keys; i++)
if ( (GNUNET_TIME_absolute_cmp (
keys->sign_keys[i].valid_from.abs_time,
<=,
GNUNET_TIME_absolute_add (now,
LIFETIME_TOLERANCE))) &&
(GNUNET_TIME_absolute_cmp (
keys->sign_keys[i].valid_until.abs_time,
>,
GNUNET_TIME_absolute_subtract (now,
LIFETIME_TOLERANCE))) &&
(0 == GNUNET_memcmp (pub,
&keys->sign_keys[i].key)) )
return GNUNET_OK;
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Signing key not valid at time %s\n",
GNUNET_TIME_absolute2s (now));
return GNUNET_SYSERR;
}
const struct TALER_EXCHANGE_DenomPublicKey *
TALER_EXCHANGE_get_denomination_key (
const struct TALER_EXCHANGE_Keys *keys,
const struct TALER_DenominationPublicKey *pk)
{
for (unsigned int i = 0; inum_denom_keys; i++)
if (0 ==
TALER_denom_pub_cmp (pk,
&keys->denom_keys[i].key))
return &keys->denom_keys[i];
return NULL;
}
const struct TALER_EXCHANGE_GlobalFee *
TALER_EXCHANGE_get_global_fee (
const struct TALER_EXCHANGE_Keys *keys,
struct GNUNET_TIME_Timestamp ts)
{
for (unsigned int i = 0; inum_global_fees; i++)
{
const struct TALER_EXCHANGE_GlobalFee *gf = &keys->global_fees[i];
if (GNUNET_TIME_timestamp_cmp (ts,
>=,
gf->start_date) &&
GNUNET_TIME_timestamp_cmp (ts,
<,
gf->end_date))
return gf;
}
return NULL;
}
struct TALER_EXCHANGE_DenomPublicKey *
TALER_EXCHANGE_copy_denomination_key (
const struct TALER_EXCHANGE_DenomPublicKey *key)
{
struct TALER_EXCHANGE_DenomPublicKey *copy;
copy = GNUNET_new (struct TALER_EXCHANGE_DenomPublicKey);
*copy = *key;
TALER_denom_pub_deep_copy (©->key,
&key->key);
return copy;
}
void
TALER_EXCHANGE_destroy_denomination_key (
struct TALER_EXCHANGE_DenomPublicKey *key)
{
TALER_denom_pub_free (&key->key);
GNUNET_free (key);
}
const struct TALER_EXCHANGE_DenomPublicKey *
TALER_EXCHANGE_get_denomination_key_by_hash (
const struct TALER_EXCHANGE_Keys *keys,
const struct TALER_DenominationHashP *hc)
{
for (unsigned int i = 0; inum_denom_keys; i++)
if (0 == GNUNET_memcmp (hc,
&keys->denom_keys[i].h_key))
return &keys->denom_keys[i];
return NULL;
}
struct TALER_EXCHANGE_Keys *
TALER_EXCHANGE_keys_incref (struct TALER_EXCHANGE_Keys *keys)
{
GNUNET_assert (keys->rc < UINT_MAX);
keys->rc++;
return keys;
}
void
TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys)
{
if (NULL == keys)
return;
GNUNET_assert (0 < keys->rc);
keys->rc--;
if (0 != keys->rc)
return;
GNUNET_array_grow (keys->sign_keys,
keys->num_sign_keys,
0);
for (unsigned int i = 0; inum_denom_keys; i++)
TALER_denom_pub_free (&keys->denom_keys[i].key);
GNUNET_array_grow (keys->denom_keys,
keys->denom_keys_size,
0);
for (unsigned int i = 0; inum_auditors; i++)
{
GNUNET_array_grow (keys->auditors[i].denom_keys,
keys->auditors[i].num_denom_keys,
0);
GNUNET_free (keys->auditors[i].auditor_url);
}
GNUNET_array_grow (keys->auditors,
keys->auditors_size,
0);
TALER_EXCHANGE_free_accounts (keys->accounts_len,
keys->accounts);
GNUNET_array_grow (keys->accounts,
keys->accounts_len,
0);
free_fees (keys->fees,
keys->fees_len);
json_decref (keys->extensions);
GNUNET_free (keys->wallet_balance_limit_without_kyc);
GNUNET_free (keys->version);
GNUNET_free (keys->currency);
GNUNET_free (keys->asset_type);
GNUNET_free (keys->global_fees);
GNUNET_free (keys->exchange_url);
GNUNET_free (keys);
}
struct TALER_EXCHANGE_Keys *
TALER_EXCHANGE_keys_from_json (const json_t *j)
{
const json_t *jkeys;
const char *url;
uint32_t version;
struct GNUNET_TIME_Timestamp expire
= GNUNET_TIME_UNIT_ZERO_TS;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_uint32 ("version",
&version),
GNUNET_JSON_spec_object_const ("keys",
&jkeys),
GNUNET_JSON_spec_string ("exchange_url",
&url),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("expire",
&expire),
NULL),
GNUNET_JSON_spec_end ()
};
struct TALER_EXCHANGE_Keys *keys;
enum TALER_EXCHANGE_VersionCompatibility compat;
if (NULL == j)
return NULL;
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
return NULL;
}
if (0 != version)
{
return NULL; /* unsupported version */
}
keys = GNUNET_new (struct TALER_EXCHANGE_Keys);
if (GNUNET_OK !=
decode_keys_json (jkeys,
false,
keys,
&compat))
{
GNUNET_break (0);
return NULL;
}
keys->rc = 1;
keys->key_data_expiration = expire;
keys->exchange_url = GNUNET_strdup (url);
return keys;
}
/**
* Data we track per denomination group.
*/
struct GroupData
{
/**
* The json blob with the group meta-data and list of denominations
*/
json_t *json;
/**
* Meta data for this group.
*/
struct TALER_DenominationGroup meta;
};
/**
* Add denomination group represented by @a value
* to list of denominations in @a cls. Also frees
* the @a value.
*
* @param[in,out] cls a `json_t *` with an array to build
* @param key unused
* @param value a `struct GroupData *`
* @return #GNUNET_OK (continue to iterate)
*/
static enum GNUNET_GenericReturnValue
add_grp (void *cls,
const struct GNUNET_HashCode *key,
void *value)
{
json_t *denominations_by_group = cls;
struct GroupData *gd = value;
const char *cipher;
json_t *ge;
bool age_restricted = gd->meta.age_mask.bits != 0;
(void) key;
switch (gd->meta.cipher)
{
case TALER_DENOMINATION_RSA:
cipher = age_restricted ? "RSA+age_restricted" : "RSA";
break;
case TALER_DENOMINATION_CS:
cipher = age_restricted ? "CS+age_restricted" : "CS";
break;
default:
GNUNET_assert (false);
}
ge = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("hash",
&gd->meta.hash),
GNUNET_JSON_pack_string ("cipher",
cipher),
GNUNET_JSON_pack_array_steal ("denoms",
gd->json),
TALER_JSON_PACK_DENOM_FEES ("fee",
&gd->meta.fees),
GNUNET_JSON_pack_allow_null (
age_restricted
? GNUNET_JSON_pack_uint64 ("age_mask",
gd->meta.age_mask.bits)
: GNUNET_JSON_pack_string ("dummy",
NULL)),
TALER_JSON_pack_amount ("value",
&gd->meta.value));
GNUNET_assert (0 ==
json_array_append_new (denominations_by_group,
ge));
GNUNET_free (gd);
return GNUNET_OK;
}
/**
* Convert array of account restrictions @a ars to JSON.
*
* @param ar_len length of @a ars
* @param ars account restrictions to convert
* @return JSON representation
*/
static json_t *
ar_to_json (unsigned int ar_len,
const struct TALER_EXCHANGE_AccountRestriction ars[static ar_len])
{
json_t *rval;
rval = json_array ();
GNUNET_assert (NULL != rval);
for (unsigned int i = 0; itype)
{
case TALER_EXCHANGE_AR_INVALID:
GNUNET_break (0);
json_decref (rval);
return NULL;
case TALER_EXCHANGE_AR_DENY:
GNUNET_assert (
0 ==
json_array_append_new (
rval,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("type",
"deny"))));
break;
case TALER_EXCHANGE_AR_REGEX:
GNUNET_assert (
0 ==
json_array_append_new (
rval,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string (
"type",
"regex"),
GNUNET_JSON_pack_string (
"regex",
ar->details.regex.posix_egrep),
GNUNET_JSON_pack_string (
"human_hint",
ar->details.regex.human_hint),
GNUNET_JSON_pack_object_incref (
"human_hint_i18n",
(json_t *) ar->details.regex.human_hint_i18n)
)));
break;
}
}
return rval;
}
json_t *
TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd)
{
struct GNUNET_TIME_Timestamp now;
json_t *keys;
json_t *signkeys;
json_t *denominations_by_group;
json_t *auditors;
json_t *recoup;
json_t *wire_fees;
json_t *accounts;
json_t *global_fees;
json_t *wblwk = NULL;
now = GNUNET_TIME_timestamp_get ();
signkeys = json_array ();
GNUNET_assert (NULL != signkeys);
for (unsigned int i = 0; inum_sign_keys; i++)
{
const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i];
json_t *signkey;
if (GNUNET_TIME_timestamp_cmp (now,
>,
sk->valid_until))
continue; /* skip keys that have expired */
signkey = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("key",
&sk->key),
GNUNET_JSON_pack_data_auto ("master_sig",
&sk->master_sig),
GNUNET_JSON_pack_timestamp ("stamp_start",
sk->valid_from),
GNUNET_JSON_pack_timestamp ("stamp_expire",
sk->valid_until),
GNUNET_JSON_pack_timestamp ("stamp_end",
sk->valid_legal));
GNUNET_assert (NULL != signkey);
GNUNET_assert (0 ==
json_array_append_new (signkeys,
signkey));
}
denominations_by_group = json_array ();
GNUNET_assert (NULL != denominations_by_group);
{
struct GNUNET_CONTAINER_MultiHashMap *dbg;
dbg = GNUNET_CONTAINER_multihashmap_create (128,
false);
for (unsigned int i = 0; inum_denom_keys; i++)
{
const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i];
struct TALER_DenominationGroup meta = {
.cipher = dk->key.cipher,
.value = dk->value,
.fees = dk->fees,
.age_mask = dk->key.age_mask
};
struct GNUNET_HashCode key;
struct GroupData *gd;
json_t *denom;
struct GNUNET_JSON_PackSpec key_spec;
if (GNUNET_TIME_timestamp_cmp (now,
>,
dk->expire_deposit))
continue; /* skip keys that have expired */
TALER_denomination_group_get_key (&meta,
&key);
gd = GNUNET_CONTAINER_multihashmap_get (dbg,
&key);
if (NULL == gd)
{
gd = GNUNET_new (struct GroupData);
gd->meta = meta;
gd->json = json_array ();
GNUNET_assert (NULL != gd->json);
GNUNET_assert (
GNUNET_OK ==
GNUNET_CONTAINER_multihashmap_put (dbg,
&key,
gd,
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
}
/* Build the running xor of the SHA512-hash of the public keys */
GNUNET_CRYPTO_hash_xor (&dk->h_key.hash,
&gd->meta.hash,
&gd->meta.hash);
switch (meta.cipher)
{
case TALER_DENOMINATION_RSA:
key_spec =
GNUNET_JSON_pack_rsa_public_key (
"rsa_pub",
dk->key.details.rsa_public_key);
break;
case TALER_DENOMINATION_CS:
key_spec =
GNUNET_JSON_pack_data_varsize (
"cs_pub",
&dk->key.details.cs_public_key,
sizeof (dk->key.details.cs_public_key));
break;
default:
GNUNET_assert (false);
}
denom = GNUNET_JSON_PACK (
GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
dk->expire_deposit),
GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
dk->withdraw_valid_until),
GNUNET_JSON_pack_timestamp ("stamp_start",
dk->valid_from),
GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
dk->expire_legal),
GNUNET_JSON_pack_data_auto ("master_sig",
&dk->master_sig),
key_spec
);
GNUNET_assert (0 ==
json_array_append_new (gd->json,
denom));
}
GNUNET_CONTAINER_multihashmap_iterate (dbg,
&add_grp,
denominations_by_group);
GNUNET_CONTAINER_multihashmap_destroy (dbg);
}
auditors = json_array ();
GNUNET_assert (NULL != auditors);
for (unsigned int i = 0; inum_auditors; i++)
{
const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i];
json_t *a;
json_t *adenoms;
adenoms = json_array ();
GNUNET_assert (NULL != adenoms);
for (unsigned int j = 0; jnum_denom_keys; j++)
{
const struct TALER_EXCHANGE_AuditorDenominationInfo *adi =
&ai->denom_keys[j];
const struct TALER_EXCHANGE_DenomPublicKey *dk =
&kd->denom_keys[adi->denom_key_offset];
json_t *k;
GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys);
if (GNUNET_TIME_timestamp_cmp (now,
>,
dk->expire_deposit))
continue; /* skip auditor signatures for denomination keys that have expired */
GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys);
k = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("denom_pub_h",
&dk->h_key),
GNUNET_JSON_pack_data_auto ("auditor_sig",
&adi->auditor_sig));
GNUNET_assert (0 ==
json_array_append_new (adenoms,
k));
}
a = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("auditor_pub",
&ai->auditor_pub),
GNUNET_JSON_pack_string ("auditor_url",
ai->auditor_url),
GNUNET_JSON_pack_array_steal ("denomination_keys",
adenoms));
GNUNET_assert (0 ==
json_array_append_new (auditors,
a));
}
global_fees = json_array ();
GNUNET_assert (NULL != global_fees);
for (unsigned int i = 0; inum_global_fees; i++)
{
const struct TALER_EXCHANGE_GlobalFee *gf
= &kd->global_fees[i];
if (GNUNET_TIME_absolute_is_past (gf->end_date.abs_time))
continue;
GNUNET_assert (
0 ==
json_array_append_new (
global_fees,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_timestamp ("start_date",
gf->start_date),
GNUNET_JSON_pack_timestamp ("end_date",
gf->end_date),
TALER_JSON_PACK_GLOBAL_FEES (&gf->fees),
GNUNET_JSON_pack_time_rel ("history_expiration",
gf->history_expiration),
GNUNET_JSON_pack_time_rel ("purse_timeout",
gf->purse_timeout),
GNUNET_JSON_pack_uint64 ("purse_account_limit",
gf->purse_account_limit),
GNUNET_JSON_pack_data_auto ("master_sig",
&gf->master_sig))));
}
accounts = json_array ();
GNUNET_assert (NULL != accounts);
for (unsigned int i = 0; iaccounts_len; i++)
{
const struct TALER_EXCHANGE_WireAccount *acc
= &kd->accounts[i];
json_t *credit_restrictions;
json_t *debit_restrictions;
credit_restrictions
= ar_to_json (acc->credit_restrictions_length,
acc->credit_restrictions);
GNUNET_assert (NULL != credit_restrictions);
debit_restrictions
= ar_to_json (acc->debit_restrictions_length,
acc->debit_restrictions);
GNUNET_assert (NULL != debit_restrictions);
GNUNET_assert (
0 ==
json_array_append_new (
accounts,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("payto_uri",
acc->payto_uri),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("conversion_url",
acc->conversion_url)),
GNUNET_JSON_pack_array_steal ("debit_restrictions",
debit_restrictions),
GNUNET_JSON_pack_array_steal ("credit_restrictions",
credit_restrictions),
GNUNET_JSON_pack_data_auto ("master_sig",
&acc->master_sig))));
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Serialized %u/%u wire accounts to JSON\n",
(unsigned int) json_array_size (accounts),
kd->accounts_len);
wire_fees = json_object ();
GNUNET_assert (NULL != wire_fees);
for (unsigned int i = 0; ifees_len; i++)
{
const struct TALER_EXCHANGE_WireFeesByMethod *fbw
= &kd->fees[i];
json_t *wf;
wf = json_array ();
GNUNET_assert (NULL != wf);
for (struct TALER_EXCHANGE_WireAggregateFees *p = fbw->fees_head;
NULL != p;
p = p->next)
{
GNUNET_assert (
0 ==
json_array_append_new (
wf,
GNUNET_JSON_PACK (
TALER_JSON_pack_amount ("wire_fee",
&p->fees.wire),
TALER_JSON_pack_amount ("closing_fee",
&p->fees.closing),
GNUNET_JSON_pack_timestamp ("start_date",
p->start_date),
GNUNET_JSON_pack_timestamp ("end_date",
p->end_date),
GNUNET_JSON_pack_data_auto ("sig",
&p->master_sig))));
}
GNUNET_assert (0 ==
json_object_set_new (wire_fees,
fbw->method,
wf));
}
recoup = json_array ();
GNUNET_assert (NULL != recoup);
for (unsigned int i = 0; inum_denom_keys; i++)
{
const struct TALER_EXCHANGE_DenomPublicKey *dk
= &kd->denom_keys[i];
if (! dk->revoked)
continue;
GNUNET_assert (0 ==
json_array_append_new (
recoup,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("h_denom_pub",
&dk->h_key))));
}
wblwk = json_array ();
GNUNET_assert (NULL != wblwk);
for (unsigned int i = 0; iwblwk_length; i++)
{
const struct TALER_Amount *a = &kd->wallet_balance_limit_without_kyc[i];
GNUNET_assert (0 ==
json_array_append_new (
wblwk,
TALER_JSON_from_amount (a)));
}
keys = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("version",
kd->version),
GNUNET_JSON_pack_string ("currency",
kd->currency),
GNUNET_JSON_pack_uint64 ("currency_fraction_digits",
kd->currency_fraction_digits),
TALER_JSON_pack_amount ("stefan_abs",
&kd->stefan_abs),
TALER_JSON_pack_amount ("stefan_log",
&kd->stefan_log),
TALER_JSON_pack_amount ("stefan_lin",
&kd->stefan_lin),
GNUNET_JSON_pack_string ("asset_type",
kd->asset_type),
GNUNET_JSON_pack_data_auto ("master_public_key",
&kd->master_pub),
GNUNET_JSON_pack_time_rel ("reserve_closing_delay",
kd->reserve_closing_delay),
GNUNET_JSON_pack_timestamp ("list_issue_date",
kd->list_issue_date),
GNUNET_JSON_pack_array_steal ("global_fees",
global_fees),
GNUNET_JSON_pack_array_steal ("signkeys",
signkeys),
GNUNET_JSON_pack_object_steal ("wire_fees",
wire_fees),
GNUNET_JSON_pack_array_steal ("accounts",
accounts),
GNUNET_JSON_pack_array_steal ("wads",
json_array ()),
GNUNET_JSON_pack_array_steal ("denominations",
denominations_by_group),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_array_steal ("recoup",
recoup)),
GNUNET_JSON_pack_array_steal ("auditors",
auditors),
GNUNET_JSON_pack_bool ("rewards_allowed",
kd->rewards_allowed),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("extensions",
kd->extensions)),
GNUNET_JSON_pack_allow_null (
GNUNET_is_zero (&kd->extensions_sig)
? GNUNET_JSON_pack_string ("dummy",
NULL)
: GNUNET_JSON_pack_data_auto ("extensions_sig",
&kd->extensions_sig)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_array_steal ("wallet_balance_limit_without_kyc",
wblwk))
);
return GNUNET_JSON_PACK (
GNUNET_JSON_pack_uint64 ("version",
EXCHANGE_SERIALIZATION_FORMAT_VERSION),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_timestamp ("expire",
kd->key_data_expiration)),
GNUNET_JSON_pack_string ("exchange_url",
kd->exchange_url),
GNUNET_JSON_pack_object_steal ("keys",
keys));
}
/* end of exchange_api_handle.c */