diff options
Diffstat (limited to 'src/lib/exchange_api_handle.c')
-rw-r--r-- | src/lib/exchange_api_handle.c | 2954 |
1 files changed, 1640 insertions, 1314 deletions
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index dfd5a3dc6..fdadc8d2a 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + 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 @@ -30,6 +30,7 @@ #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" @@ -39,12 +40,17 @@ * Which version of the Taler protocol is implemented * by this library? Used to determine compatibility. */ -#define EXCHANGE_PROTOCOL_CURRENT 9 +#define EXCHANGE_PROTOCOL_CURRENT 19 /** * How many versions are we backwards compatible with? */ -#define EXCHANGE_PROTOCOL_AGE 0 +#define EXCHANGE_PROTOCOL_AGE 2 + +/** + * Set to 1 for extra debug logging. + */ +#define DEBUG 0 /** * Current version for (local) JSON serialization of persisted @@ -53,191 +59,322 @@ #define EXCHANGE_SERIALIZATION_FORMAT_VERSION 0 /** - * Set to 1 for extra debug logging. + * How far off do we allow key lifetimes to be? */ -#define DEBUG 0 +#define LIFETIME_TOLERANCE GNUNET_TIME_UNIT_HOURS /** - * Log error related to CURL operations. - * - * @param type log level - * @param function which function failed to run - * @param code what was the curl error code + * If the "Expire" cache control header is missing, for + * how long do we assume the reply to be valid at least? */ -#define CURL_STRERROR(type, function, code) \ - GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \ - function, __FILE__, __LINE__, curl_easy_strerror (code)); - +#define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS /** - * Data for the request to get the /keys of a exchange. + * If the "Expire" cache control header is missing, for + * how long do we assume the reply to be valid at least? */ -struct KeysRequest; +#define MINIMUM_EXPIRATION GNUNET_TIME_relative_multiply ( \ + GNUNET_TIME_UNIT_MINUTES, 2) /** - * Entry in DLL of auditors used by an exchange. + * Handle for a GET /keys request. */ -struct TEAH_AuditorListEntry +struct TALER_EXCHANGE_GetKeysHandle { - /** - * Next pointer of DLL. - */ - struct TEAH_AuditorListEntry *next; /** - * Prev pointer of DLL. + * The exchange base URL (i.e. "https://exchange.demo.taler.net/") */ - struct TEAH_AuditorListEntry *prev; + char *exchange_url; /** - * Base URL of the auditor. + * The url for the /keys request. */ - char *auditor_url; + char *url; /** - * Handle to the auditor. + * Previous /keys response, NULL for none. */ - struct TALER_AUDITOR_Handle *ah; + struct TALER_EXCHANGE_Keys *prev_keys; /** - * Head of DLL of interactions with this auditor. + * Entry for this request with the `struct GNUNET_CURL_Context`. */ - struct TEAH_AuditorInteractionEntry *ai_head; + struct GNUNET_CURL_Job *job; /** - * Tail of DLL of interactions with this auditor. + * Expiration time according to "Expire:" header. + * 0 if not provided by the server. */ - struct TEAH_AuditorInteractionEntry *ai_tail; + struct GNUNET_TIME_Timestamp expire; /** - * Public key of the auditor. + * Function to call with the exchange's certification data, + * NULL if this has already been done. */ - struct TALER_AuditorPublicKeyP auditor_pub; + TALER_EXCHANGE_GetKeysCallback cert_cb; /** - * Flag indicating that the auditor is available and that protocol - * version compatibility is given. + * Closure to pass to @e cert_cb. */ - int is_up; + void *cert_cb_cls; }; -/* ***************** Internal /keys fetching ************* */ - /** - * Data for the request to get the /keys of a exchange. + * Element in the `struct SignatureContext` array. */ -struct KeysRequest +struct SignatureElement { + /** - * The connection to exchange this request handle will use + * Offset of the denomination in the group array, + * for sorting (2nd rank, ascending). */ - struct TALER_EXCHANGE_Handle *exchange; + unsigned int offset; /** - * The url for this handle + * Offset of the group in the denominations array, + * for sorting (2nd rank, ascending). */ - char *url; + unsigned int group_offset; /** - * Entry for this request with the `struct GNUNET_CURL_Context`. + * Pointer to actual master signature to hash over. */ - struct GNUNET_CURL_Job *job; + struct TALER_MasterSignatureP master_sig; +}; +/** + * Context for collecting the array of master signatures + * needed to verify the exchange_sig online signature. + */ +struct SignatureContext +{ /** - * Expiration time according to "Expire:" header. - * 0 if not provided by the server. + * Array of signatures to hash over. + */ + struct SignatureElement *elements; + + /** + * Write offset in the @e elements array. */ - struct GNUNET_TIME_Absolute expire; + unsigned int elements_pos; + /** + * Allocated space for @e elements. + */ + unsigned int elements_size; }; /** - * Signature of functions called with the result from our call to the - * auditor's /deposit-confirmation handler. + * Determine order to sort two elements by before + * we hash the master signatures. Used for + * sorting with qsort(). * - * @param cls closure of type `struct TEAH_AuditorInteractionEntry *` - * @param hr HTTP response + * @param a pointer to a `struct SignatureElement` + * @param b pointer to a `struct SignatureElement` + * @return 0 if equal, -1 if a < b, 1 if a > b. */ -void -TEAH_acc_confirmation_cb (void *cls, - const struct TALER_AUDITOR_HttpResponse *hr) +static int +signature_context_sort_cb (const void *a, + const void *b) { - struct TEAH_AuditorInteractionEntry *aie = cls; - struct TEAH_AuditorListEntry *ale = aie->ale; + const struct SignatureElement *sa = a; + const struct SignatureElement *sb = b; - if (MHD_HTTP_OK != hr->http_status) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to submit deposit confirmation to auditor `%s' with HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n", - ale->auditor_url, - hr->http_status, - hr->ec); - } - GNUNET_CONTAINER_DLL_remove (ale->ai_head, - ale->ai_tail, - aie); - GNUNET_free (aie); + if (sa->group_offset < sb->group_offset) + return -1; + if (sa->group_offset > sb->group_offset) + return 1; + if (sa->offset < sb->offset) + return -1; + if (sa->offset > sb->offset) + return 1; + /* We should never have two disjoint elements + with same time and offset */ + GNUNET_assert (sa == sb); + return 0; } /** - * Iterate over all available auditors for @a h, calling - * @a ac and giving it a chance to start a deposit - * confirmation interaction. + * Append a @a master_sig to the @a sig_ctx using the + * given attributes for (later) sorting. * - * @param h exchange to go over auditors for - * @param ac function to call per auditor - * @param ac_cls closure for @a ac + * @param[in,out] sig_ctx signature context to update + * @param group_offset offset for the group + * @param offset offset for the entry + * @param master_sig master signature for the entry */ -void -TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h, - TEAH_AuditorCallback ac, - void *ac_cls) +static void +append_signature (struct SignatureContext *sig_ctx, + unsigned int group_offset, + unsigned int offset, + const struct TALER_MasterSignatureP *master_sig) { - if (NULL == h->auditors_head) + struct SignatureElement *element; + unsigned int new_size; + + if (sig_ctx->elements_pos == sig_ctx->elements_size) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "No auditor available for exchange `%s'. Not submitting deposit confirmations.\n", - h->url); - return; + if (0 == sig_ctx->elements_size) + new_size = 1024; + else + new_size = sig_ctx->elements_size * 2; + GNUNET_array_grow (sig_ctx->elements, + sig_ctx->elements_size, + new_size); } - for (struct TEAH_AuditorListEntry *ale = h->auditors_head; - NULL != ale; - ale = ale->next) + element = &sig_ctx->elements[sig_ctx->elements_pos++]; + element->offset = offset; + element->group_offset = group_offset; + element->master_sig = *master_sig; +} + + +/** + * 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; i<wfm_len; i++) { - struct TEAH_AuditorInteractionEntry *aie; + struct TALER_EXCHANGE_WireFeesByMethod *wfmi = &wfm[i]; - if (GNUNET_NO == ale->is_up) - continue; - aie = ac (ac_cls, - ale->ah, - &ale->auditor_pub); - if (NULL != aie) + while (NULL != wfmi->fees_head) { - aie->ale = ale; - GNUNET_CONTAINER_DLL_insert (ale->ai_head, - ale->ai_tail, - aie); + struct TALER_EXCHANGE_WireAggregateFees *fe + = wfmi->fees_head; + + wfmi->fees_head = fe->next; + GNUNET_free (fe); } + GNUNET_free (wfmi->method); } + GNUNET_free (wfm); } /** - * Release memory occupied by a keys request. Note that this does not - * cancel the request itself. + * Parse wire @a fees and return array. * - * @param kr request to free + * @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 void -free_keys_request (struct KeysRequest *kr) +static struct TALER_EXCHANGE_WireFeesByMethod * +parse_fees (const struct TALER_MasterPublicKeyP *master_pub, + const char *currency, + const json_t *fees, + unsigned int *fees_len) { - GNUNET_free (kr->url); - GNUNET_free (kr); + struct TALER_EXCHANGE_WireFeesByMethod *fbm; + size_t fbml = json_object_size (fees); + unsigned int i = 0; + const char *key; + const json_t *fee_array; + + if (UINT_MAX < fbml) + { + GNUNET_break (0); + return NULL; + } + fbm = GNUNET_new_array (fbml, + struct TALER_EXCHANGE_WireFeesByMethod); + *fees_len = (unsigned int) fbml; + json_object_foreach ((json_t *) fees, key, fee_array) { + struct TALER_EXCHANGE_WireFeesByMethod *fe = &fbm[i++]; + size_t 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; i<keys->num_auditors; i++) + { + const struct TALER_EXCHANGE_AuditorInformation *auditor + = &keys->auditors[i]; + + ac (ac_cls, + auditor->auditor_url, + &auditor->auditor_pub); + } } @@ -252,29 +389,28 @@ free_keys_request (struct KeysRequest *kr) * * @param[out] sign_key where to return the result * @param check_sigs should we check signatures? - * @param[in] sign_key_obj json to parse + * @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 json malformed. + * invalid or the @a sign_key_obj is malformed. */ -static int +static enum GNUNET_GenericReturnValue parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, - int check_sigs, - json_t *sign_key_obj, + bool check_sigs, + const json_t *sign_key_obj, const struct TALER_MasterPublicKeyP *master_key) { - struct TALER_MasterSignatureP sign_key_issue_sig; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("master_sig", - &sign_key_issue_sig), + &sign_key->master_sig), GNUNET_JSON_spec_fixed_auto ("key", &sign_key->key), - TALER_JSON_spec_absolute_time ("stamp_start", - &sign_key->valid_from), - TALER_JSON_spec_absolute_time ("stamp_expire", - &sign_key->valid_until), - TALER_JSON_spec_absolute_time ("stamp_end", - &sign_key->valid_legal), + 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 () }; @@ -286,7 +422,6 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, GNUNET_break_op (0); return GNUNET_SYSERR; } - if (! check_sigs) return GNUNET_OK; if (GNUNET_OK != @@ -296,57 +431,64 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, sign_key->valid_until, sign_key->valid_legal, master_key, - &sign_key_issue_sig)) + &sign_key->master_sig)) { GNUNET_break_op (0); return GNUNET_SYSERR; } - sign_key->master_sig = sign_key_issue_sig; return GNUNET_OK; } /** - * Parse a exchange's denomination key encoded in JSON. + * 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[in] denom_key_obj json to parse + * @param denom_key_obj json to parse * @param master_key master key to use to verify signature - * @param hash_context where to accumulate data for signature verification + * @param group_offset offset for the group + * @param index index of this denomination key in the group + * @param sig_ctx where to write details about encountered + * master signatures, NULL if not used * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is * invalid or the json malformed. */ -static int -parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key, - int check_sigs, - json_t *denom_key_obj, - struct TALER_MasterPublicKeyP *master_key, - struct GNUNET_HashContext *hash_context) +static enum GNUNET_GenericReturnValue +parse_json_denomkey_partially ( + struct TALER_EXCHANGE_DenomPublicKey *denom_key, + enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher, + bool check_sigs, + const json_t *denom_key_obj, + struct TALER_MasterPublicKeyP *master_key, + unsigned int group_offset, + unsigned int index, + struct SignatureContext *sig_ctx) { struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("master_sig", &denom_key->master_sig), - TALER_JSON_spec_absolute_time ("stamp_expire_deposit", - &denom_key->expire_deposit), - TALER_JSON_spec_absolute_time ("stamp_expire_withdraw", - &denom_key->withdraw_valid_until), - TALER_JSON_spec_absolute_time ("stamp_start", - &denom_key->valid_from), - TALER_JSON_spec_absolute_time ("stamp_expire_legal", - &denom_key->expire_legal), - TALER_JSON_spec_amount_any ("value", - &denom_key->value), - TALER_JSON_spec_amount_any ("fee_withdraw", - &denom_key->fee_withdraw), - TALER_JSON_spec_amount_any ("fee_deposit", - &denom_key->fee_deposit), - TALER_JSON_spec_amount_any ("fee_refresh", - &denom_key->fee_refresh), - TALER_JSON_spec_amount_any ("fee_refund", - &denom_key->fee_refund), - GNUNET_JSON_spec_rsa_public_key ("denom_pub", - &denom_key->key.rsa_public_key), + 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 () }; @@ -358,13 +500,13 @@ parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key, GNUNET_break_op (0); return GNUNET_SYSERR; } - - GNUNET_CRYPTO_rsa_public_key_hash (denom_key->key.rsa_public_key, - &denom_key->h_key); - if (NULL != hash_context) - GNUNET_CRYPTO_hash_context_read (hash_context, - &denom_key->h_key, - sizeof (struct GNUNET_HashCode)); + TALER_denom_pub_hash (&denom_key->key, + &denom_key->h_key); + if (NULL != sig_ctx) + append_signature (sig_ctx, + group_offset, + index, + &denom_key->master_sig); if (! check_sigs) return GNUNET_OK; EXITIF (GNUNET_SYSERR == @@ -375,19 +517,16 @@ parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key, denom_key->expire_deposit, denom_key->expire_legal, &denom_key->value, - &denom_key->fee_withdraw, - &denom_key->fee_deposit, - &denom_key->fee_refresh, - &denom_key->fee_refund, + &denom_key->fees, master_key, &denom_key->master_sig)); return GNUNET_OK; EXITIF_exit: + GNUNET_JSON_parse_free (spec); /* invalidate denom_key, just to be sure */ memset (denom_key, 0, sizeof (*denom_key)); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } @@ -397,30 +536,29 @@ EXITIF_exit: * * @param[out] auditor where to return the result * @param check_sigs should we check signatures - * @param[in] auditor_obj json to parse + * @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 int +static enum GNUNET_GenericReturnValue parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, - int check_sigs, - json_t *auditor_obj, + bool check_sigs, + const json_t *auditor_obj, const struct TALER_EXCHANGE_Keys *key_data) { - json_t *keys; + const json_t *keys; json_t *key; - unsigned int len; - unsigned int off; - unsigned int i; + size_t off; + size_t 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", + TALER_JSON_spec_web_url ("auditor_url", &auditor_url), - GNUNET_JSON_spec_json ("denomination_keys", - &keys), + GNUNET_JSON_spec_array_const ("denomination_keys", + &keys), GNUNET_JSON_spec_end () }; @@ -438,16 +576,15 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, return GNUNET_SYSERR; } auditor->auditor_url = GNUNET_strdup (auditor_url); - len = json_array_size (keys); - auditor->denom_keys = GNUNET_new_array (len, - struct - TALER_EXCHANGE_AuditorDenominationInfo); - off = 0; - json_array_foreach (keys, i, key) { + 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 GNUNET_HashCode denom_h; - const struct TALER_EXCHANGE_DenomPublicKey *dk; - unsigned int dk_off; + 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), @@ -464,8 +601,6 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, GNUNET_break_op (0); continue; } - dk = NULL; - dk_off = UINT_MAX; for (unsigned int j = 0; j<key_data->num_denom_keys; j++) { if (0 == GNUNET_memcmp (&denom_h, @@ -480,7 +615,7 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Auditor signed denomination %s, which we do not know. Ignoring signature.\n", - GNUNET_h2s (&denom_h)); + GNUNET_h2s (&denom_h.hash)); continue; } if (check_sigs) @@ -495,125 +630,95 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, dk->expire_deposit, dk->expire_legal, &dk->value, - &dk->fee_withdraw, - &dk->fee_deposit, - &dk->fee_refresh, - &dk->fee_refund, + &dk->fees, &auditor->auditor_pub, &auditor_sig)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } } - auditor->denom_keys[off].denom_key_offset = dk_off; - auditor->denom_keys[off].auditor_sig = auditor_sig; - off++; + auditor->denom_keys[pos].denom_key_offset = dk_off; + auditor->denom_keys[pos].auditor_sig = auditor_sig; + pos++; } - auditor->num_denom_keys = off; - GNUNET_JSON_parse_free (spec); + if (pos > UINT_MAX) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + auditor->num_denom_keys = (unsigned int) pos; return GNUNET_OK; } /** - * Function called with information about the auditor. Marks an - * auditor as 'up'. + * Parse a exchange's global fee information encoded in JSON. * - * @param cls closure, a `struct TEAH_AuditorListEntry *` - * @param hr http response from the auditor - * @param vi basic information about the auditor - * @param compat protocol compatibility information + * @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 void -auditor_version_cb ( - void *cls, - const struct TALER_AUDITOR_HttpResponse *hr, - const struct TALER_AUDITOR_VersionInformation *vi, - enum TALER_AUDITOR_VersionCompatibility compat) +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 TEAH_AuditorListEntry *ale = cls; - - if (NULL == vi) - { - /* In this case, we don't mark the auditor as 'up' */ - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Auditor `%s' gave unexpected version response.\n", - ale->auditor_url); - return; - } + 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 (0 != (TALER_AUDITOR_VC_INCOMPATIBLE & compat)) + if (GNUNET_OK != + GNUNET_JSON_parse (fee_obj, + spec, + NULL, NULL)) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Auditor `%s' runs incompatible protocol version!\n", - ale->auditor_url); - if (0 != (TALER_AUDITOR_VC_OLDER & compat)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Auditor `%s' runs outdated protocol version!\n", - ale->auditor_url); - } - if (0 != (TALER_AUDITOR_VC_NEWER & compat)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Auditor `%s' runs more recent incompatible version. We should upgrade!\n", - ale->auditor_url); - } - return; + GNUNET_break_op (0); +#if DEBUG + json_dumpf (fee_obj, + stderr, + JSON_INDENT (2)); +#endif + return GNUNET_SYSERR; } - ale->is_up = GNUNET_YES; -} - - -/** - * Recalculate our auditor list, we got /keys and it may have - * changed. - * - * @param exchange exchange for which to update the list. - */ -static void -update_auditors (struct TALER_EXCHANGE_Handle *exchange) -{ - struct TALER_EXCHANGE_Keys *kd = &exchange->key_data; - - TALER_LOG_DEBUG ("Updating auditors\n"); - for (unsigned int i = 0; i<kd->num_auditors; i++) + if (check_sigs) { - /* Compare auditor data from /keys with auditor data - * from owned exchange structures. */ - struct TALER_EXCHANGE_AuditorInformation *auditor = &kd->auditors[i]; - struct TEAH_AuditorListEntry *ale = NULL; - - for (struct TEAH_AuditorListEntry *a = exchange->auditors_head; - NULL != a; - a = a->next) + 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)) { - if (0 == GNUNET_memcmp (&auditor->auditor_pub, - &a->auditor_pub)) - { - ale = a; - break; - } + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; } - if (NULL != ale) - continue; /* found, no need to add */ - - /* new auditor, add */ - TALER_LOG_DEBUG ("Found new auditor!\n"); - ale = GNUNET_new (struct TEAH_AuditorListEntry); - ale->auditor_pub = auditor->auditor_pub; - ale->auditor_url = GNUNET_strdup (auditor->auditor_url); - GNUNET_CONTAINER_DLL_insert (exchange->auditors_head, - exchange->auditors_tail, - ale); - - ale->ah = TALER_AUDITOR_connect (exchange->ctx, - ale->auditor_url, - &auditor_version_cb, - ale); } + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; } @@ -623,39 +728,31 @@ update_auditors (struct TALER_EXCHANGE_Handle *exchange) * @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. + * the same object), non-zero otherwise. */ static unsigned int -denoms_cmp (struct TALER_EXCHANGE_DenomPublicKey *denom1, - struct TALER_EXCHANGE_DenomPublicKey *denom2) +denoms_cmp (const struct TALER_EXCHANGE_DenomPublicKey *denom1, + const struct TALER_EXCHANGE_DenomPublicKey *denom2) { - struct GNUNET_CRYPTO_RsaPublicKey *tmp1; - struct GNUNET_CRYPTO_RsaPublicKey *tmp2; - int r1; - int r2; - int ret; - - /* First check if pub is the same. */ - if (0 != GNUNET_CRYPTO_rsa_public_key_cmp - (denom1->key.rsa_public_key, - denom2->key.rsa_public_key)) - return 1; + struct TALER_EXCHANGE_DenomPublicKey tmp1; + struct TALER_EXCHANGE_DenomPublicKey tmp2; - tmp1 = denom1->key.rsa_public_key; - tmp2 = denom2->key.rsa_public_key; - r1 = denom1->revoked; - r2 = denom2->revoked; - - denom1->key.rsa_public_key = NULL; - denom2->key.rsa_public_key = NULL; - /* Then proceed with the rest of the object. */ - ret = GNUNET_memcmp (denom1, - denom2); - denom1->revoked = r1; - denom2->revoked = r2; - denom1->key.rsa_public_key = tmp1; - denom2->key.rsa_public_key = tmp2; - return ret; + 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); } @@ -670,33 +767,27 @@ denoms_cmp (struct TALER_EXCHANGE_DenomPublicKey *denom1, * @return #GNUNET_OK on success, #GNUNET_SYSERR on error * (malformed JSON) */ -static int +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 sig; - struct GNUNET_HashContext *hash_context; - struct TALER_ExchangePublicKeyP pub; - const char *currency; - struct GNUNET_JSON_Specification mspec[] = { - GNUNET_JSON_spec_fixed_auto ("eddsa_sig", - &sig), - GNUNET_JSON_spec_fixed_auto ("eddsa_pub", - &pub), - /* sig and pub must be first, as we skip those if - check_sig is false! */ - GNUNET_JSON_spec_fixed_auto ("master_public_key", - &key_data->master_pub), - TALER_JSON_spec_absolute_time ("list_issue_date", - &key_data->list_issue_date), - TALER_JSON_spec_relative_time ("reserve_closing_delay", - &key_data->reserve_closing_delay), - GNUNET_JSON_spec_string ("currency", - ¤cy), - GNUNET_JSON_spec_end () - }; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_ExchangePublicKeyP exchange_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; + struct SignatureContext sig_ctx = { 0 }; if (JSON_OBJECT != json_typeof (resp_obj)) { @@ -708,16 +799,12 @@ decode_keys_json (const json_t *resp_obj, stderr, JSON_INDENT (2)); #endif - /* check the version */ + /* check the version first */ { - const char *ver; - unsigned int age; - unsigned int revision; - unsigned int current; - char dummy; + struct TALER_JSON_ProtocolVersion pv; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("version", - &ver), + TALER_JSON_spec_version ("version", + &pv), GNUNET_JSON_spec_end () }; @@ -729,140 +816,378 @@ decode_keys_json (const json_t *resp_obj, 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) + if (EXCHANGE_PROTOCOL_CURRENT < pv.current) { *vc |= TALER_EXCHANGE_VC_NEWER; - if (EXCHANGE_PROTOCOL_CURRENT < current - age) + if (EXCHANGE_PROTOCOL_CURRENT < pv.current - pv.age) *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; } - if (EXCHANGE_PROTOCOL_CURRENT > current) + if (EXCHANGE_PROTOCOL_CURRENT > pv.current) { *vc |= TALER_EXCHANGE_VC_OLDER; - if (EXCHANGE_PROTOCOL_CURRENT - EXCHANGE_PROTOCOL_AGE > current) + if (EXCHANGE_PROTOCOL_CURRENT - EXCHANGE_PROTOCOL_AGE > pv.current) *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; } - key_data->version = GNUNET_strdup (ver); } - hash_context = NULL; - EXITIF (GNUNET_OK != + { + const char *ver; + const char *currency; + const char *asset_type; + struct GNUNET_JSON_Specification mspec[] = { + GNUNET_JSON_spec_fixed_auto ( + "exchange_sig", + &exchange_sig), + GNUNET_JSON_spec_fixed_auto ( + "exchange_pub", + &exchange_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_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_string ("version", + &ver), + 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_currency_specification ( + "currency_specification", + currency, + &key_data->cspec), + TALER_JSON_spec_amount ( + "stefan_abs", + currency, + &key_data->stefan_abs), + TALER_JSON_spec_amount ( + "stefan_log", + currency, + &key_data->stefan_log), + GNUNET_JSON_spec_double ( + "stefan_lin", + &key_data->stefan_lin), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != GNUNET_JSON_parse (resp_obj, - (check_sig) ? mspec : &mspec[2], - NULL, NULL)); - key_data->currency = GNUNET_strdup (currency); - /* parse the master public key and issue date of the response */ - if (check_sig) - hash_context = GNUNET_CRYPTO_hash_context_start (); + sspec, + &emsg, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Parsing /keys failed for `%s' (%u)\n", + emsg, + eline); + EXITIF (1); + } + } + + key_data->currency = GNUNET_strdup (currency); + key_data->version = GNUNET_strdup (ver); + key_data->asset_type = GNUNET_strdup (asset_type); + if (! no_extensions) + key_data->extensions = json_incref ((json_t *) manifests); + } + + /* parse the global fees */ + EXITIF (json_array_size (global_fees) > UINT_MAX); + key_data->num_global_fees + = (unsigned int) json_array_size (global_fees); + if (0 != key_data->num_global_fees) + { + json_t *global_fee; + size_t 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 */ + EXITIF (json_array_size (sign_keys_array) > UINT_MAX); + key_data->num_sign_keys + = (unsigned int) json_array_size (sign_keys_array); + if (0 != key_data->num_sign_keys) { - json_t *sign_keys_array; json_t *sign_key_obj; - unsigned int index; + size_t 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)); + } + } - EXITIF (NULL == (sign_keys_array = - json_object_get (resp_obj, - "signkeys"))); - EXITIF (JSON_ARRAY != json_typeof (sign_keys_array)); - if (0 != (key_data->num_sign_keys = - json_array_size (sign_keys_array))) + /* Parse balance limits */ + if (NULL != wblwk) + { + EXITIF (json_array_size (wblwk) > UINT_MAX); + key_data->wblwk_length + = (unsigned int) 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; i<key_data->wblwk_length; i++) { - 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)); - } + 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 the denomination keys, merging with the - possibly EXISTING array as required (/keys cherry picking) */ + /* 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 */ + EXITIF (json_array_size (accounts) > UINT_MAX); + 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", + key_data->accounts_len); + + + /* Parse the supported extension(s): age-restriction. */ + /* TODO: maybe lift all this into a FP in TALER_Extension ? */ + if (! no_extensions) { - json_t *denom_keys_array; - json_t *denom_key_obj; - unsigned int index; + 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)); + } - EXITIF (NULL == (denom_keys_array = - json_object_get (resp_obj, - "denoms"))); - EXITIF (JSON_ARRAY != json_typeof (denom_keys_array)); + /* 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 (); + } - json_array_foreach (denom_keys_array, index, denom_key_obj) { - struct TALER_EXCHANGE_DenomPublicKey dk; - bool found = false; + /* + * 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; - memset (&dk, - 0, - sizeof (dk)); - EXITIF (GNUNET_SYSERR == - parse_json_denomkey (&dk, - check_sig, - denom_key_obj, - &key_data->master_pub, - hash_context)); + json_array_foreach (denominations_by_group, + group_idx, + group_obj) + { + /* 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; - for (unsigned int j = 0; - j<key_data->num_denom_keys; - j++) + 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) { - if (0 == denoms_cmp (&dk, - &key_data->denom_keys[j])) + /* 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 = { + .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, + group_idx, + index, + check_sig + ? &sig_ctx + : NULL)); + for (unsigned int j = 0; + j<key_data->num_denom_keys; + j++) { - found = true; - break; + 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"); - GNUNET_CRYPTO_rsa_public_key_free (dk.key.rsa_public_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_from %s\n", - GNUNET_STRINGS_absolute_time_to_string (dk.valid_from)); - key_data->last_denom_issue_date - = GNUNET_TIME_absolute_max (key_data->last_denom_issue_date, - dk.valid_from); - }; - } + + 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); + GNUNET_assert (key_data->denom_keys_size > + key_data->num_denom_keys); + GNUNET_assert (key_data->num_denom_keys < UINT_MAX); + 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 */ + } /* end of json_array_foreach over groups of denominations */ + } /* end of scope for group_ojb/group_idx */ /* parse the auditor information */ { - json_t *auditors_array; json_t *auditor_info; unsigned int index; - EXITIF (NULL == (auditors_array = - json_object_get (resp_obj, - "auditors"))); - EXITIF (JSON_ARRAY != json_typeof (auditors_array)); - /* Merge with the existing auditor information we have (/keys cherry picking) */ - json_array_foreach (auditors_array, index, auditor_info) { + json_array_foreach (auditors_array, index, auditor_info) + { struct TALER_EXCHANGE_AuditorInformation ai; bool found = false; @@ -921,174 +1246,94 @@ decode_keys_json (const json_t *resp_obj, GNUNET_array_grow (key_data->auditors, key_data->auditors_size, key_data->auditors_size * 2 + 2); + GNUNET_assert (key_data->auditors_size > + key_data->num_auditors); GNUNET_assert (NULL != ai.auditor_url); + GNUNET_assert (key_data->num_auditors < UINT_MAX); key_data->auditors[key_data->num_auditors++] = ai; }; } /* parse the revocation/recoup information */ + if (NULL != recoup_array) { - json_t *recoup_array; json_t *recoup_info; unsigned int index; - if (NULL != (recoup_array = - json_object_get (resp_obj, - "recoup"))) + json_array_foreach (recoup_array, index, recoup_info) { - EXITIF (JSON_ARRAY != json_typeof (recoup_array)); - - json_array_foreach (recoup_array, index, recoup_info) { - struct GNUNET_HashCode h_denom_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("h_denom_pub", - &h_denom_pub), - GNUNET_JSON_spec_end () - }; + 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; - j<key_data->num_denom_keys; - j++) + EXITIF (GNUNET_OK != + GNUNET_JSON_parse (recoup_info, + spec, + NULL, NULL)); + for (unsigned int j = 0; + j<key_data->num_denom_keys; + j++) + { + if (0 == GNUNET_memcmp (&h_denom_pub, + &key_data->denom_keys[j].h_key)) { - if (0 == GNUNET_memcmp (&h_denom_pub, - &key_data->denom_keys[j].h_key)) - { - key_data->denom_keys[j].revoked = GNUNET_YES; - break; - } + key_data->denom_keys[j].revoked = true; + break; } - }; + } } } if (check_sig) { - struct TALER_ExchangeKeySetPS ks = { - .purpose.size = htonl (sizeof (ks)), - .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET), - .list_issue_date = GNUNET_TIME_absolute_hton (key_data->list_issue_date) - }; + struct GNUNET_HashContext *hash_context; + struct GNUNET_HashCode hc; + hash_context = GNUNET_CRYPTO_hash_context_start (); + qsort (sig_ctx.elements, + sig_ctx.elements_pos, + sizeof (struct SignatureElement), + &signature_context_sort_cb); + for (unsigned int i = 0; i<sig_ctx.elements_pos; i++) + { + struct SignatureElement *element = &sig_ctx.elements[i]; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Adding %u,%u,%s\n", + element->group_offset, + element->offset, + TALER_B2S (&element->master_sig)); + GNUNET_CRYPTO_hash_context_read (hash_context, + &element->master_sig, + sizeof (element->master_sig)); + } + GNUNET_array_grow (sig_ctx.elements, + sig_ctx.elements_size, + 0); GNUNET_CRYPTO_hash_context_finish (hash_context, - &ks.hc); - hash_context = NULL; + &hc); EXITIF (GNUNET_OK != TALER_EXCHANGE_test_signing_key (key_data, - &pub)); + &exchange_pub)); EXITIF (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_KEY_SET, - &ks, - &sig.eddsa_signature, - &pub.eddsa_pub)); + TALER_exchange_online_key_set_verify ( + key_data->list_issue_date, + &hc, + &exchange_pub, + &exchange_sig)); } return GNUNET_OK; -EXITIF_exit: +EXITIF_exit: *vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR; - if (NULL != hash_context) - GNUNET_CRYPTO_hash_context_abort (hash_context); return GNUNET_SYSERR; } /** - * Free key data object. - * - * @param key_data data to free (pointer itself excluded) - */ -static void -free_key_data (struct TALER_EXCHANGE_Keys *key_data) -{ - GNUNET_array_grow (key_data->sign_keys, - key_data->num_sign_keys, - 0); - for (unsigned int i = 0; i<key_data->num_denom_keys; i++) - GNUNET_CRYPTO_rsa_public_key_free ( - key_data->denom_keys[i].key.rsa_public_key); - - GNUNET_array_grow (key_data->denom_keys, - key_data->denom_keys_size, - 0); - for (unsigned int i = 0; i<key_data->num_auditors; i++) - { - GNUNET_array_grow (key_data->auditors[i].denom_keys, - key_data->auditors[i].num_denom_keys, - 0); - GNUNET_free (key_data->auditors[i].auditor_url); - } - GNUNET_array_grow (key_data->auditors, - key_data->auditors_size, - 0); - GNUNET_free (key_data->version); - key_data->version = NULL; - GNUNET_free (key_data->currency); - key_data->currency = NULL; -} - - -/** - * Initiate download of /keys from the exchange. - * - * @param cls exchange where to download /keys from - */ -static void -request_keys (void *cls); - - -/** - * Let the user set the last valid denomination time manually. - * - * @param exchange the exchange handle. - * @param last_denom_new new last denomination time. - */ -void -TALER_EXCHANGE_set_last_denom (struct TALER_EXCHANGE_Handle *exchange, - struct GNUNET_TIME_Absolute last_denom_new) -{ - exchange->key_data.last_denom_issue_date = last_denom_new; -} - - -/** - * Check if our current response for /keys is valid, and if - * not trigger download. - * - * @param exchange exchange to check keys for - * @param flags options controlling when to download what - * @return until when the response is current, 0 if we are re-downloading - */ -struct GNUNET_TIME_Absolute -TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange, - enum TALER_EXCHANGE_CheckKeysFlags flags) -{ - bool force_download = 0 != (flags & TALER_EXCHANGE_CKF_FORCE_DOWNLOAD); - bool pull_all_keys = 0 != (flags & TALER_EXCHANGE_CKF_PULL_ALL_KEYS); - - if (NULL != exchange->kr) - return GNUNET_TIME_UNIT_ZERO_ABS; - - if (pull_all_keys) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Forcing re-download of all exchange keys\n"); - GNUNET_break (GNUNET_YES == force_download); - exchange->state = MHS_INIT; - } - if ( (! force_download) && - (GNUNET_TIME_absolute_is_future (exchange->key_data_expiration)) ) - return exchange->key_data_expiration; - if (NULL == exchange->retry_task) - exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys, - exchange); - return GNUNET_TIME_UNIT_ZERO_ABS; -} - - -/** * Callback used when downloading the reply to a /keys request * is complete. * @@ -1101,119 +1346,122 @@ keys_completed_cb (void *cls, long response_code, const void *resp_obj) { - struct KeysRequest *kr = cls; - struct TALER_EXCHANGE_Handle *exchange = kr->exchange; - struct TALER_EXCHANGE_Keys kd; - struct TALER_EXCHANGE_Keys kd_old; - enum TALER_EXCHANGE_VersionCompatibility vc; + struct TALER_EXCHANGE_GetKeysHandle *gkh = cls; const json_t *j = resp_obj; - struct TALER_EXCHANGE_HttpResponse hr = { - .reply = j, - .http_status = (unsigned int) response_code + 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.\n", - kr->url, - response_code); - kd_old = exchange->key_data; - memset (&kd, - 0, - sizeof (struct TALER_EXCHANGE_Keys)); - vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR; + "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)) + { + if (MHD_HTTP_OK == response_code) + 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: - free_keys_request (kr); - exchange->keys_error_count++; - exchange->kr = NULL; - GNUNET_assert (NULL == exchange->retry_task); - exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay); - exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay, - &request_keys, - exchange); - return; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to receive /keys response from exchange %s\n", + gkh->exchange_url); + break; case MHD_HTTP_OK: - exchange->keys_error_count = 0; if (NULL == j) { + GNUNET_break (0); response_code = 0; break; } - /* 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... */ - memcpy (kd.denom_keys, - kd_old.denom_keys, - kd_old.num_denom_keys * sizeof (struct - TALER_EXCHANGE_DenomPublicKey)); - - for (unsigned int i = 0; i<kd_old.num_denom_keys; i++) - kd.denom_keys[i].key.rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_dup ( - kd_old.denom_keys[i].key.rsa_public_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; i<kd_old.num_auditors; i++) + kd = GNUNET_new (struct TALER_EXCHANGE_Keys); + kd->exchange_url = GNUNET_strdup (gkh->exchange_url); + if (NULL != gkh->prev_keys) { - const struct TALER_EXCHANGE_AuditorInformation *aold = - &kd_old.auditors[i]; - struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i]; - - anew->auditor_pub = aold->auditor_pub; - GNUNET_assert (NULL != aold->auditor_url); - anew->auditor_url = GNUNET_strdup (aold->auditor_url); - GNUNET_array_grow (anew->denom_keys, - anew->num_denom_keys, - aold->num_denom_keys); - memcpy (anew->denom_keys, - aold->denom_keys, - aold->num_denom_keys - * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); - } + 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; i<kd_old->num_denom_keys; i++) + TALER_denom_pub_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; i<kd_old->num_auditors; i++) + { + const struct TALER_EXCHANGE_AuditorInformation *aold = + &kd_old->auditors[i]; + struct TALER_EXCHANGE_AuditorInformation *anew = &kd->auditors[i]; - /* Old auditors got just copied into new ones. */ + 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, - &vc)) + kd, + &kresp.details.ok.compat)) { TALER_LOG_ERROR ("Could not decode /keys response\n"); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - for (unsigned int i = 0; i<kd.num_auditors; i++) - { - struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i]; - - GNUNET_array_grow (anew->denom_keys, - anew->num_denom_keys, - 0); - GNUNET_free (anew->auditor_url); - } - GNUNET_free (kd.auditors); - kd.auditors = NULL; - kd.num_auditors = 0; - for (unsigned int i = 0; i<kd_old.num_denom_keys; i++) - GNUNET_CRYPTO_rsa_public_key_free (kd.denom_keys[i].key.rsa_public_key); - GNUNET_array_grow (kd.denom_keys, - kd.denom_keys_size, - 0); - kd.num_denom_keys = 0; + kd->rc = 1; + TALER_EXCHANGE_keys_decref (kd); + kd = NULL; + kresp.hr.http_status = 0; + kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - json_decref (exchange->key_data_raw); - exchange->key_data_raw = json_deep_copy (j); - exchange->retry_delay = GNUNET_TIME_UNIT_ZERO; + 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: @@ -1221,121 +1469,36 @@ keys_completed_cb (void *cls, case MHD_HTTP_NOT_FOUND: if (NULL == j) { - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.hint = TALER_ErrorCode_get_hint (hr.ec); + kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec); } else { - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + kresp.hr.ec = TALER_JSON_get_error_code (j); + kresp.hr.hint = TALER_JSON_get_error_hint (j); } break; default: - if (MHD_HTTP_GATEWAY_TIMEOUT == response_code) - exchange->keys_error_count++; if (NULL == j) { - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.hint = TALER_ErrorCode_get_hint (hr.ec); + kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec); } else { - hr.ec = TALER_JSON_get_error_code (j); - hr.hint = TALER_JSON_get_error_hint (j); + 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) hr.ec); + (int) kresp.hr.ec); break; } - exchange->key_data = kd; - TALER_LOG_DEBUG ("Last DK issue date update to: %s\n", - GNUNET_STRINGS_absolute_time_to_string - (exchange->key_data.last_denom_issue_date)); - - - if (MHD_HTTP_OK != response_code) - { - exchange->kr = NULL; - free_keys_request (kr); - exchange->state = MHS_FAILED; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Exchange keys download failed\n"); - if (NULL != exchange->key_data_raw) - { - json_decref (exchange->key_data_raw); - exchange->key_data_raw = NULL; - } - free_key_data (&kd_old); - /* notify application that we failed */ - exchange->cert_cb (exchange->cert_cb_cls, - &hr, - NULL, - vc); - return; - } - - exchange->kr = NULL; - exchange->key_data_expiration = kr->expire; - free_keys_request (kr); - exchange->state = MHS_CERT; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Successfully downloaded exchange's keys\n"); - update_auditors (exchange); - /* notify application about the key information */ - exchange->cert_cb (exchange->cert_cb_cls, - &hr, - &exchange->key_data, - vc); - free_key_data (&kd_old); -} - - -/* ********************* library internal API ********* */ - - -/** - * Get the context of a exchange. - * - * @param h the exchange handle to query - * @return ctx context to execute jobs in - */ -struct GNUNET_CURL_Context * -TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h) -{ - return h->ctx; -} - - -/** - * Check if the handle is ready to process requests. - * - * @param h the exchange handle to query - * @return #GNUNET_YES if we are ready, #GNUNET_NO if not - */ -int -TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h) -{ - return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO; -} - - -/** - * Obtain the URL to use for an API request. - * - * @param h handle for the exchange - * @param path Taler API path (i.e. "/reserve/withdraw") - * @return the full URL to use with cURL - */ -char * -TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h, - const char *path) -{ - GNUNET_assert ('/' == path[0]); - return TALER_url_join (h->url, - path + 1, - NULL); + gkh->cert_cb (gkh->cert_cb_cls, + &kresp, + kd); + TALER_EXCHANGE_get_keys_cancel (gkh); } @@ -1349,12 +1512,12 @@ TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h, * Parse HTTP timestamp. * * @param dateline header to parse header - * @param at where to write the result + * @param[out] at where to write the result * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue parse_date_string (const char *dateline, - struct GNUNET_TIME_Absolute *at) + struct GNUNET_TIME_Timestamp *at) { static const char *MONTHS[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", @@ -1436,7 +1599,7 @@ parse_date_string (const char *dateline, } if (t < 0) t = 0; /* can happen due to timezone issues if date was 1.1.1970 */ - at->abs_value_us = 1000LL * 1000LL * t; + *at = GNUNET_TIME_timestamp_from_s (t); return GNUNET_OK; } @@ -1449,7 +1612,7 @@ parse_date_string (const char *dateline, * @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 KeysRequest` + * @param userdata the `struct TALER_EXCHANGE_GetKeysHandle` * @return `size * nitems` on success (everything else aborts) */ static size_t @@ -1458,7 +1621,7 @@ header_cb (char *buffer, size_t nitems, void *userdata) { - struct KeysRequest *kr = userdata; + struct TALER_EXCHANGE_GetKeysHandle *kr = userdata; size_t total = size * nitems; char *val; @@ -1470,6 +1633,10 @@ header_cb (char *buffer, 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)) @@ -1478,205 +1645,579 @@ header_cb (char *buffer, "Failed to parse %s-header `%s'\n", MHD_HTTP_HEADER_EXPIRES, val); - kr->expire = GNUNET_TIME_UNIT_ZERO_ABS; + kr->expire = GNUNET_TIME_UNIT_ZERO_TS; } GNUNET_free (val); return total; } -/* ********************* public API ******************* */ +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, + 1)); + 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; +} -/** - * Deserialize the key data and use it to bootstrap @a exchange to - * more efficiently recover the state. Errors in @a data must be - * tolerated (i.e. by re-downloading instead). - * - * @param exchange which exchange's key and wire data should be deserialized - * @param data the data to deserialize - */ -static void -deserialize_data (struct TALER_EXCHANGE_Handle *exchange, - const json_t *data) + +void +TALER_EXCHANGE_get_keys_cancel ( + struct TALER_EXCHANGE_GetKeysHandle *gkh) { - enum TALER_EXCHANGE_VersionCompatibility vc; - json_t *keys; + 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; i<keys->num_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; i<keys->num_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; i<keys->num_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_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; i<keys->num_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; i<keys->num_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; i<keys->num_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->cspec.name); + json_decref (keys->cspec.map_alt_unit_names); + 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_Absolute expire; + struct GNUNET_TIME_Timestamp expire + = GNUNET_TIME_UNIT_ZERO_TS; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_uint32 ("version", &version), - GNUNET_JSON_spec_json ("keys", - &keys), - GNUNET_JSON_spec_string ("exchange_url", + GNUNET_JSON_spec_object_const ("keys", + &jkeys), + TALER_JSON_spec_web_url ("exchange_url", &url), - TALER_JSON_spec_absolute_time ("expire", - &expire), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("expire", + &expire), + NULL), GNUNET_JSON_spec_end () }; - struct TALER_EXCHANGE_Keys key_data; - struct TALER_EXCHANGE_HttpResponse hr = { - .ec = TALER_EC_NONE, - .http_status = MHD_HTTP_OK, - .reply = data - }; + struct TALER_EXCHANGE_Keys *keys; + enum TALER_EXCHANGE_VersionCompatibility compat; - if (NULL == data) - return; + if (NULL == j) + return NULL; if (GNUNET_OK != - GNUNET_JSON_parse (data, + GNUNET_JSON_parse (j, spec, NULL, NULL)) { GNUNET_break_op (0); - return; + return NULL; } if (0 != version) { - GNUNET_JSON_parse_free (spec); - return; /* unsupported version */ + return NULL; /* unsupported version */ } - if (0 != strcmp (url, - exchange->url)) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - return; - } - memset (&key_data, - 0, - sizeof (struct TALER_EXCHANGE_Keys)); + keys = GNUNET_new (struct TALER_EXCHANGE_Keys); if (GNUNET_OK != - decode_keys_json (keys, + decode_keys_json (jkeys, false, - &key_data, - &vc)) + keys, + &compat)) { GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - return; + return NULL; } - /* decode successful, initialize with the result */ - GNUNET_assert (NULL == exchange->key_data_raw); - exchange->key_data_raw = json_deep_copy (keys); - exchange->key_data = key_data; - exchange->key_data_expiration = expire; - exchange->state = MHS_CERT; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Successfully loaded exchange's keys via deserialization\n"); - update_auditors (exchange); - /* notify application about the key information */ - exchange->cert_cb (exchange->cert_cb_cls, - &hr, - &exchange->key_data, - vc); - GNUNET_JSON_parse_free (spec); + keys->rc = 1; + keys->key_data_expiration = expire; + keys->exchange_url = GNUNET_strdup (url); + return keys; } /** - * Serialize the latest key data from @a - * exchange to be persisted on disk (to be used with - * #TALER_EXCHANGE_OPTION_DATA to more efficiently recover - * the state). + * 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 exchange which exchange's key and wire data should be - * serialized - * @return NULL on error (i.e. no current data available); - * otherwise JSON object owned by the caller + * @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 GNUNET_CRYPTO_BSA_RSA: + cipher = age_restricted ? "RSA+age_restricted" : "RSA"; + break; + case GNUNET_CRYPTO_BSA_CS: + cipher = age_restricted ? "CS+age_restricted" : "CS"; + break; + default: + GNUNET_assert (false); + } + + ge = GNUNET_JSON_PACK ( + 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; i<ar_len; i++) + { + const struct TALER_EXCHANGE_AccountRestriction *ar = &ars[i]; + + switch (ar->type) + { + 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 ( + "payto_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_serialize_data (struct TALER_EXCHANGE_Handle *exchange) +TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) { - const struct TALER_EXCHANGE_Keys *kd = &exchange->key_data; - struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Timestamp now; json_t *keys; json_t *signkeys; - json_t *denoms; + 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_absolute_get (); + now = GNUNET_TIME_timestamp_get (); signkeys = json_array (); - if (NULL == signkeys) - { - GNUNET_break (0); - return NULL; - } + GNUNET_assert (NULL != signkeys); for (unsigned int i = 0; i<kd->num_sign_keys; i++) { const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i]; json_t *signkey; - if (now.abs_value_us > sk->valid_until.abs_value_us) + 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_time_abs ("stamp_start", - sk->valid_from), - GNUNET_JSON_pack_time_abs ("stamp_expire", - sk->valid_until), - GNUNET_JSON_pack_time_abs ("stamp_end", - sk->valid_legal)); - if (NULL == signkey) - { - GNUNET_break (0); - continue; - } - if (0 != json_array_append_new (signkeys, - signkey)) - { - GNUNET_break (0); - json_decref (signkey); - json_decref (signkeys); - return NULL; - } - } - denoms = json_array (); - if (NULL == denoms) - { - GNUNET_break (0); - json_decref (signkeys); - return NULL; + 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)); } - for (unsigned int i = 0; i<kd->num_denom_keys; i++) + + denominations_by_group = json_array (); + GNUNET_assert (NULL != denominations_by_group); { - const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i]; - json_t *denom; + struct GNUNET_CONTAINER_MultiHashMap *dbg; - if (now.abs_value_us > dk->expire_deposit.abs_value_us) - continue; /* skip keys that have expired */ - denom = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_time_abs ("stamp_expire_deposit", - dk->expire_deposit), - GNUNET_JSON_pack_time_abs ("stamp_expire_withdraw", - dk->withdraw_valid_until), - GNUNET_JSON_pack_time_abs ("stamp_start", - dk->valid_from), - GNUNET_JSON_pack_time_abs ("stamp_expire_legal", - dk->expire_legal), - TALER_JSON_pack_amount ("value", - &dk->value), - TALER_JSON_pack_amount ("fee_withdraw", - &dk->fee_withdraw), - TALER_JSON_pack_amount ("fee_deposit", - &dk->fee_deposit), - TALER_JSON_pack_amount ("fee_refresh", - &dk->fee_refresh), - TALER_JSON_pack_amount ("fee_refund", - &dk->fee_refund), - GNUNET_JSON_pack_data_auto ("master_sig", - &dk->master_sig), - TALER_JSON_pack_denomination_public_key ("denom_pub", - &dk->key)); - GNUNET_assert (0 == - json_array_append_new (denoms, - denom)); + dbg = GNUNET_CONTAINER_multihashmap_create (128, + false); + for (unsigned int i = 0; i<kd->num_denom_keys; i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i]; + struct TALER_DenominationGroup meta = { + .cipher = dk->key.bsign_pub_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)); + + } + switch (meta.cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + key_spec = + GNUNET_JSON_pack_rsa_public_key ( + "rsa_pub", + dk->key.bsign_pub_key->details.rsa_public_key); + break; + case GNUNET_CRYPTO_BSA_CS: + key_spec = + GNUNET_JSON_pack_data_varsize ( + "cs_pub", + &dk->key.bsign_pub_key->details.cs_public_key, + sizeof (dk->key.bsign_pub_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; i<kd->num_auditors; i++) @@ -1686,14 +2227,7 @@ TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange) json_t *adenoms; adenoms = json_array (); - if (NULL == adenoms) - { - GNUNET_break (0); - json_decref (denoms); - json_decref (signkeys); - json_decref (auditors); - return NULL; - } + GNUNET_assert (NULL != adenoms); for (unsigned int j = 0; j<ai->num_denom_keys; j++) { const struct TALER_EXCHANGE_AuditorDenominationInfo *adi = @@ -1702,7 +2236,10 @@ TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange) &kd->denom_keys[adi->denom_key_offset]; json_t *k; - if (now.abs_value_us > dk->expire_deposit.abs_value_us) + 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 ( @@ -1726,420 +2263,209 @@ TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange) json_array_append_new (auditors, a)); } - keys = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("version", - kd->version), - GNUNET_JSON_pack_string ("currency", - kd->currency), - 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_time_abs ("list_issue_date", - kd->list_issue_date), - GNUNET_JSON_pack_array_steal ("signkeys", - signkeys), - GNUNET_JSON_pack_array_steal ("denoms", - denoms), - GNUNET_JSON_pack_array_steal ("auditors", - auditors)); - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("version", - EXCHANGE_SERIALIZATION_FORMAT_VERSION), - GNUNET_JSON_pack_time_abs ("expire", - exchange->key_data_expiration), - GNUNET_JSON_pack_string ("exchange_url", - exchange->url), - GNUNET_JSON_pack_object_steal ("keys", - keys)); -} - - -/** - * Initialise a connection to the exchange. Will connect to the - * exchange and obtain information about the exchange's master - * public key and the exchange's auditor. - * The respective information will be passed to the @a cert_cb - * once available, and all future interactions with the exchange - * will be checked to be signed (where appropriate) by the - * respective master key. - * - * @param ctx the context - * @param url HTTP base URL for the exchange - * @param cert_cb function to call with the exchange's - * certification information - * @param cert_cb_cls closure for @a cert_cb - * @param ... list of additional arguments, - * terminated by #TALER_EXCHANGE_OPTION_END. - * @return the exchange handle; NULL upon error - */ -struct TALER_EXCHANGE_Handle * -TALER_EXCHANGE_connect ( - struct GNUNET_CURL_Context *ctx, - const char *url, - TALER_EXCHANGE_CertificationCallback cert_cb, - void *cert_cb_cls, - ...) -{ - struct TALER_EXCHANGE_Handle *exchange; - va_list ap; - enum TALER_EXCHANGE_Option opt; - TALER_LOG_DEBUG ("Connecting to the exchange (%s)\n", - url); - /* Disable 100 continue processing */ - GNUNET_break (GNUNET_OK == - GNUNET_CURL_append_header (ctx, - "Expect:")); - exchange = GNUNET_new (struct TALER_EXCHANGE_Handle); - exchange->ctx = ctx; - exchange->url = GNUNET_strdup (url); - exchange->cert_cb = cert_cb; - exchange->cert_cb_cls = cert_cb_cls; - exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys, - exchange); - va_start (ap, cert_cb_cls); - while (TALER_EXCHANGE_OPTION_END != - (opt = va_arg (ap, int))) + global_fees = json_array (); + GNUNET_assert (NULL != global_fees); + for (unsigned int i = 0; i<kd->num_global_fees; i++) { - switch (opt) - { - case TALER_EXCHANGE_OPTION_END: - GNUNET_assert (0); - break; - case TALER_EXCHANGE_OPTION_DATA: - { - const json_t *data = va_arg (ap, const json_t *); + const struct TALER_EXCHANGE_GlobalFee *gf + = &kd->global_fees[i]; - deserialize_data (exchange, - data); - break; - } - default: - GNUNET_assert (0); - break; - } - } - va_end (ap); - return exchange; -} - - -/** - * Compute the network timeout for the next request to /keys. - * - * @param exchange the exchange handle - * @returns the timeout in seconds (for use by CURL) - */ -static long -get_keys_timeout_seconds (struct TALER_EXCHANGE_Handle *exchange) -{ - unsigned int kec; - - /* if retry counter >= 8, do not bother to go further, we - stop the exponential back-off at 128 anyway. */ - kec = GNUNET_MIN (7, - exchange->keys_error_count); - return GNUNET_MIN (120, - 5 + (1L << kec)); -} - - -/** - * Initiate download of /keys from the exchange. - * - * @param cls exchange where to download /keys from - */ -static void -request_keys (void *cls) -{ - struct TALER_EXCHANGE_Handle *exchange = cls; - struct KeysRequest *kr; - CURL *eh; - char url[200] = "/keys?"; - - exchange->retry_task = NULL; - GNUNET_assert (NULL == exchange->kr); - kr = GNUNET_new (struct KeysRequest); - kr->exchange = exchange; - - if (GNUNET_YES == TEAH_handle_is_ready (exchange)) - { - TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n", - GNUNET_STRINGS_absolute_time_to_string ( - exchange->key_data.last_denom_issue_date)); - sprintf (&url[strlen (url)], - "last_issue_date=%llu&", - (unsigned long - long) exchange->key_data.last_denom_issue_date.abs_value_us - / 1000000LLU); + 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)))); } - /* Clean the last '&'/'?' sign that we optimistically put. */ - url[strlen (url) - 1] = '\0'; - kr->url = TEAH_path_to_url (exchange, - url); - if (NULL == kr->url) + accounts = json_array (); + GNUNET_assert (NULL != accounts); + for (unsigned int i = 0; i<kd->accounts_len; i++) { - struct TALER_EXCHANGE_HttpResponse hr = { - .ec = TALER_EC_GENERIC_CONFIGURATION_INVALID - }; - - GNUNET_free (kr); - exchange->keys_error_count++; - exchange->state = MHS_FAILED; - exchange->cert_cb (exchange->cert_cb_cls, - &hr, - NULL, - TALER_EXCHANGE_VC_PROTOCOL_ERROR); - return; + 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_int64 ("priority", + acc->priority), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("bank_label", + acc->bank_label)), + 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, - "Requesting keys with URL `%s'.\n", - kr->url); - eh = TALER_EXCHANGE_curl_easy_get_ (kr->url); - if (NULL == eh) - { - GNUNET_free (kr->url); - GNUNET_free (kr); - exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay); - exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay, - &request_keys, - exchange); - return; - } - GNUNET_break (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_VERBOSE, - 0)); - GNUNET_break (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_TIMEOUT, - get_keys_timeout_seconds (exchange))); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_HEADERFUNCTION, - &header_cb)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_HEADERDATA, - kr)); - kr->job = GNUNET_CURL_job_add_with_ct_json (exchange->ctx, - eh, - &keys_completed_cb, - kr); - exchange->kr = kr; -} - - -/** - * Disconnect from the exchange - * - * @param exchange the exchange handle - */ -void -TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange) -{ - struct TEAH_AuditorListEntry *ale; + "Serialized %u/%u wire accounts to JSON\n", + (unsigned int) json_array_size (accounts), + kd->accounts_len); - while (NULL != (ale = exchange->auditors_head)) + wire_fees = json_object (); + GNUNET_assert (NULL != wire_fees); + for (unsigned int i = 0; i<kd->fees_len; i++) { - struct TEAH_AuditorInteractionEntry *aie; - - while (NULL != (aie = ale->ai_head)) + 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 (aie->ale == ale); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Not sending deposit confirmation to auditor `%s' due to exchange disconnect\n", - ale->auditor_url); - TALER_AUDITOR_deposit_confirmation_cancel (aie->dch); - GNUNET_CONTAINER_DLL_remove (ale->ai_head, - ale->ai_tail, - aie); - GNUNET_free (aie); + 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_CONTAINER_DLL_remove (exchange->auditors_head, - exchange->auditors_tail, - ale); - TALER_LOG_DEBUG ("Disconnecting the auditor `%s'\n", - ale->auditor_url); - TALER_AUDITOR_disconnect (ale->ah); - GNUNET_free (ale->auditor_url); - GNUNET_free (ale); - } - if (NULL != exchange->kr) - { - GNUNET_CURL_job_cancel (exchange->kr->job); - free_keys_request (exchange->kr); - exchange->kr = NULL; - } - free_key_data (&exchange->key_data); - if (NULL != exchange->key_data_raw) - { - json_decref (exchange->key_data_raw); - exchange->key_data_raw = NULL; + GNUNET_assert (0 == + json_object_set_new (wire_fees, + fbw->method, + wf)); } - if (NULL != exchange->retry_task) + + recoup = json_array (); + GNUNET_assert (NULL != recoup); + for (unsigned int i = 0; i<kd->num_denom_keys; i++) { - GNUNET_SCHEDULER_cancel (exchange->retry_task); - exchange->retry_task = NULL; + 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)))); } - GNUNET_free (exchange->url); - GNUNET_free (exchange); -} - - -/** - * Test if the given @a pub is a the current signing key from the exchange - * according to @a keys. - * - * @param keys the exchange's key set - * @param pub claimed current online signing key for the exchange - * @return #GNUNET_OK if @a pub is (according to /keys) a current signing key - */ -int -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; i<keys->num_sign_keys; i++) - if ( (keys->sign_keys[i].valid_from.abs_value_us <= now.abs_value_us + 60 - * 60 * 1000LL * 1000LL) && - (keys->sign_keys[i].valid_until.abs_value_us > now.abs_value_us - 60 - * 60 * 1000LL * 1000LL) && - (0 == GNUNET_memcmp (pub, - &keys->sign_keys[i].key)) ) - return GNUNET_OK; - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Signing key not valid at time %llu\n", - (unsigned long long) now.abs_value_us); - return GNUNET_SYSERR; -} - - -/** - * Get exchange's base URL. - * - * @param exchange exchange handle. - * @return the base URL from the handle. - */ -const char * -TALER_EXCHANGE_get_base_url (const struct TALER_EXCHANGE_Handle *exchange) -{ - return exchange->url; -} + wblwk = json_array (); + GNUNET_assert (NULL != wblwk); + for (unsigned int i = 0; i<kd->wblwk_length; i++) + { + const struct TALER_Amount *a = &kd->wallet_balance_limit_without_kyc[i]; -/** - * Obtain the denomination key details from the exchange. - * - * @param keys the exchange's key set - * @param pk public key of the denomination to lookup - * @return details about the given denomination key, NULL if the key is - * not found - */ -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; i<keys->num_denom_keys; i++) - if (0 == GNUNET_CRYPTO_rsa_public_key_cmp (pk->rsa_public_key, - keys->denom_keys[i].key. - rsa_public_key)) - return &keys->denom_keys[i]; - return NULL; -} - - -/** - * Create a copy of a denomination public key. - * - * @param key key to copy - * @returns a copy, must be freed with #TALER_EXCHANGE_destroy_denomination_key - */ -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; - copy->key.rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup ( - key->key.rsa_public_key); - - return copy; -} - - -/** - * Destroy a denomination public key. - * Should only be called with keys created by #TALER_EXCHANGE_copy_denomination_key. - * - * @param key key to destroy. - */ -void -TALER_EXCHANGE_destroy_denomination_key ( - struct TALER_EXCHANGE_DenomPublicKey *key) -{ - GNUNET_CRYPTO_rsa_public_key_free (key->key.rsa_public_key);; - GNUNET_free (key); -} - - -/** - * Obtain the denomination key details from the exchange. - * - * @param keys the exchange's key set - * @param hc hash of the public key of the denomination to lookup - * @return details about the given denomination key - */ -const struct TALER_EXCHANGE_DenomPublicKey * -TALER_EXCHANGE_get_denomination_key_by_hash ( - const struct TALER_EXCHANGE_Keys *keys, - const struct GNUNET_HashCode *hc) -{ - for (unsigned int i = 0; i<keys->num_denom_keys; i++) - if (0 == GNUNET_memcmp (hc, - &keys->denom_keys[i].h_key)) - return &keys->denom_keys[i]; - return NULL; -} - - -/** - * Obtain the keys from the exchange. - * - * @param exchange the exchange handle - * @return the exchange's key set - */ -const struct TALER_EXCHANGE_Keys * -TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange) -{ - (void) TALER_EXCHANGE_check_keys_current (exchange, - TALER_EXCHANGE_CKF_NONE); - return &exchange->key_data; -} - + GNUNET_assert (0 == + json_array_append_new ( + wblwk, + TALER_JSON_from_amount (a))); + } -/** - * Obtain the keys from the exchange in the - * raw JSON format - * - * @param exchange the exchange handle - * @return the exchange's keys in raw JSON - */ -json_t * -TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange) -{ - (void) TALER_EXCHANGE_check_keys_current (exchange, - TALER_EXCHANGE_CKF_NONE); - return json_deep_copy (exchange->key_data_raw); + keys = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("version", + kd->version), + GNUNET_JSON_pack_string ("currency", + kd->currency), + GNUNET_JSON_pack_object_steal ("currency_specification", + TALER_CONFIG_currency_specs_to_json ( + &kd->cspec)), + TALER_JSON_pack_amount ("stefan_abs", + &kd->stefan_abs), + TALER_JSON_pack_amount ("stefan_log", + &kd->stefan_log), + GNUNET_JSON_pack_double ("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)); } |