diff options
author | Christian Grothoff <christian@grothoff.org> | 2019-01-11 21:27:34 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2019-01-11 21:43:15 +0100 |
commit | 54fc83ee6b910d482948c6ec8185df7aab1b0cb1 (patch) | |
tree | 10c04cad1392659a9ccef469271f866e393ebb48 /src/lib/exchange_api_handle.c | |
parent | 57ab9f9fdba607fcc3817adf58f37c5390f8d220 (diff) |
fix cyclic dependency by combining exchange-lib and auditor-lib directories
Diffstat (limited to 'src/lib/exchange_api_handle.c')
-rw-r--r-- | src/lib/exchange_api_handle.c | 1779 |
1 files changed, 1779 insertions, 0 deletions
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c new file mode 100644 index 000000000..9743b1f09 --- /dev/null +++ b/src/lib/exchange_api_handle.c @@ -0,0 +1,1779 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file exchange-lib/exchange_api_handle.c + * @brief Implementation of the "handle" component of the exchange's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <microhttpd.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "taler_auditor_service.h" +#include "taler_signatures.h" +#include "exchange_api_handle.h" +#include "exchange_api_curl_defaults.h" +#include "backoff.h" + +/** + * Which revision of the Taler protocol is implemented + * by this library? Used to determine compatibility. + */ +#define TALER_PROTOCOL_CURRENT 2 + +/** + * How many revisions back are we compatible to? + */ +#define TALER_PROTOCOL_AGE 0 + +/** + * Current version for (local) JSON serialization of persisted + * /keys data. + */ +#define TALER_SERIALIZATION_FORMAT_VERSION 0 + + +/** + * 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 + */ +#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)); + +/** + * Stages of initialization for the `struct TALER_EXCHANGE_Handle` + */ +enum ExchangeHandleState +{ + /** + * Just allocated. + */ + MHS_INIT = 0, + + /** + * Obtained the exchange's certification data and keys. + */ + MHS_CERT = 1, + + /** + * Failed to initialize (fatal). + */ + MHS_FAILED = 2 +}; + + +/** + * Data for the request to get the /keys of a exchange. + */ +struct KeysRequest; + + +/** + * Entry in list of ongoing interactions with an auditor. + */ +struct AuditorInteractionEntry +{ + /** + * DLL entry. + */ + struct AuditorInteractionEntry *next; + + /** + * DLL entry. + */ + struct AuditorInteractionEntry *prev; + + /** + * Interaction state. + */ + struct TALER_AUDITOR_DepositConfirmationHandle *dch; +}; + + +/** + * Entry in DLL of auditors used by an exchange. + */ +struct AuditorListEntry +{ + /** + * Next pointer of DLL. + */ + struct AuditorListEntry *next; + + /** + * Prev pointer of DLL. + */ + struct AuditorListEntry *prev; + + /** + * Base URL of the auditor. + */ + const char *auditor_url; + + /** + * Handle to the auditor. + */ + struct TALER_AUDITOR_Handle *ah; + + /** + * Head of DLL of interactions with this auditor. + */ + struct AuditorInteractionEntry *ai_head; + + /** + * Tail of DLL of interactions with this auditor. + */ + struct AuditorInteractionEntry *ai_tail; + + /** + * Public key of the auditor. + */ + struct TALER_AuditorPublicKeyP auditor_pub; + + /** + * Flag indicating that the auditor is available and that protocol + * version compatibility is given. + */ + int is_up; + +}; + + +/** + * Handle to the exchange + */ +struct TALER_EXCHANGE_Handle +{ + /** + * The context of this handle + */ + struct GNUNET_CURL_Context *ctx; + + /** + * The URL of the exchange (i.e. "http://exchange.taler.net/") + */ + char *url; + + /** + * Function to call with the exchange's certification data, + * NULL if this has already been done. + */ + TALER_EXCHANGE_CertificationCallback cert_cb; + + /** + * Closure to pass to @e cert_cb. + */ + void *cert_cb_cls; + + /** + * Data for the request to get the /keys of a exchange, + * NULL once we are past stage #MHS_INIT. + */ + struct KeysRequest *kr; + + /** + * Task for retrying /keys request. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * Raw key data of the exchange, only valid if + * @e handshake_complete is past stage #MHS_CERT. + */ + json_t *key_data_raw; + + /** + * Head of DLL of auditors of this exchange. + */ + struct AuditorListEntry *auditors_head; + + /** + * Tail of DLL of auditors of this exchange. + */ + struct AuditorListEntry *auditors_tail; + + /** + * Key data of the exchange, only valid if + * @e handshake_complete is past stage #MHS_CERT. + */ + struct TALER_EXCHANGE_Keys key_data; + + /** + * Retry /keys frequency. + */ + struct GNUNET_TIME_Relative retry_delay; + + /** + * When does @e key_data expire? + */ + struct GNUNET_TIME_Absolute key_data_expiration; + + /** + * Stage of the exchange's initialization routines. + */ + enum ExchangeHandleState state; + +}; + + +/* ***************** Internal /keys fetching ************* */ + +/** + * Data for the request to get the /keys of a exchange. + */ +struct KeysRequest +{ + /** + * The connection to exchange this request handle will use + */ + struct TALER_EXCHANGE_Handle *exchange; + + /** + * The url for this handle + */ + char *url; + + /** + * Entry for this request with the `struct GNUNET_CURL_Context`. + */ + struct GNUNET_CURL_Job *job; + + /** + * Expiration time according to "Expire:" header. + * 0 if not provided by the server. + */ + struct GNUNET_TIME_Absolute expire; + +}; + + +/** + * Iterate over all available auditors for @a h, calling + * @param ah and giving it a chance to start a deposit + * confirmation interaction. + * + * @param h exchange to go over auditors for + * @param ac function to call per auditor + * @param ac_cls closure for @a ac + */ +void +TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h, + TEAH_AuditorCallback ac, + void *ac_cls) +{ + // FIXME! +} + + +/** + * Release memory occupied by a keys request. + * Note that this does not cancel the request + * itself. + * + * @param kr request to free + */ +static void +free_keys_request (struct KeysRequest *kr) +{ + GNUNET_free (kr->url); + GNUNET_free (kr); +} + + +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +/** + * Parse a exchange's signing key encoded in JSON. + * + * @param[out] sign_key where to return the result + * @param check_sigs should we check signatures? + * @param[in] 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. + */ +static int +parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key, + int check_sigs, + json_t *sign_key_obj, + const struct TALER_MasterPublicKeyP *master_key) +{ + struct TALER_ExchangeSigningKeyValidityPS sign_key_issue; + struct TALER_MasterSignatureP sign_key_issue_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("master_sig", + &sign_key_issue_sig), + GNUNET_JSON_spec_fixed_auto ("key", + &sign_key->key), + GNUNET_JSON_spec_absolute_time ("stamp_start", + &sign_key->valid_from), + GNUNET_JSON_spec_absolute_time ("stamp_expire", + &sign_key->valid_until), + GNUNET_JSON_spec_absolute_time ("stamp_end", + &sign_key->valid_legal), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (sign_key_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (! check_sigs) + return GNUNET_OK; + sign_key_issue.signkey_pub = sign_key->key; + sign_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY); + sign_key_issue.purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS)); + sign_key_issue.master_public_key = *master_key; + sign_key_issue.start = GNUNET_TIME_absolute_hton (sign_key->valid_from); + sign_key_issue.expire = GNUNET_TIME_absolute_hton (sign_key->valid_until); + sign_key_issue.end = GNUNET_TIME_absolute_hton (sign_key->valid_legal); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY, + &sign_key_issue.purpose, + &sign_key_issue_sig.eddsa_signature, + &master_key->eddsa_pub)) + { + 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. + * + * @param[out] denom_key where to return the result + * @param check_sigs should we check signatures? + * @param[in] 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 + * @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) +{ + struct TALER_DenominationKeyValidityPS denom_key_issue; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("master_sig", + &denom_key->master_sig), + GNUNET_JSON_spec_absolute_time ("stamp_expire_deposit", + &denom_key->expire_deposit), + GNUNET_JSON_spec_absolute_time ("stamp_expire_withdraw", + &denom_key->withdraw_valid_until), + GNUNET_JSON_spec_absolute_time ("stamp_start", + &denom_key->valid_from), + GNUNET_JSON_spec_absolute_time ("stamp_expire_legal", + &denom_key->expire_legal), + TALER_JSON_spec_amount ("value", + &denom_key->value), + TALER_JSON_spec_amount ("fee_withdraw", + &denom_key->fee_withdraw), + TALER_JSON_spec_amount ("fee_deposit", + &denom_key->fee_deposit), + TALER_JSON_spec_amount ("fee_refresh", + &denom_key->fee_refresh), + TALER_JSON_spec_amount ("fee_refund", + &denom_key->fee_refund), + GNUNET_JSON_spec_rsa_public_key ("denom_pub", + &denom_key->key.rsa_public_key), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (denom_key_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + GNUNET_CRYPTO_rsa_public_key_hash (denom_key->key.rsa_public_key, + &denom_key->h_key); + if (! check_sigs) + return GNUNET_OK; + memset (&denom_key_issue, + 0, + sizeof (denom_key_issue)); + denom_key_issue.purpose.purpose + = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY); + denom_key_issue.purpose.size + = htonl (sizeof (struct TALER_DenominationKeyValidityPS)); + denom_key_issue.master = *master_key; + denom_key_issue.denom_hash = denom_key->h_key; + denom_key_issue.start = GNUNET_TIME_absolute_hton (denom_key->valid_from); + denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (denom_key->withdraw_valid_until); + denom_key_issue.expire_deposit = GNUNET_TIME_absolute_hton (denom_key->expire_deposit); + denom_key_issue.expire_legal = GNUNET_TIME_absolute_hton (denom_key->expire_legal); + TALER_amount_hton (&denom_key_issue.value, + &denom_key->value); + TALER_amount_hton (&denom_key_issue.fee_withdraw, + &denom_key->fee_withdraw); + TALER_amount_hton (&denom_key_issue.fee_deposit, + &denom_key->fee_deposit); + TALER_amount_hton (&denom_key_issue.fee_refresh, + &denom_key->fee_refresh); + TALER_amount_hton (&denom_key_issue.fee_refund, + &denom_key->fee_refund); + EXITIF (GNUNET_SYSERR == + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY, + &denom_key_issue.purpose, + &denom_key->master_sig.eddsa_signature, + &master_key->eddsa_pub)); + GNUNET_CRYPTO_hash_context_read (hash_context, + &denom_key_issue.denom_hash, + sizeof (struct GNUNET_HashCode)); + return GNUNET_OK; + + EXITIF_exit: + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; +} + + +/** + * Parse a exchange's auditor information encoded in JSON. + * + * @param[out] auditor where to return the result + * @param check_sig should we check signatures + * @param[in] 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 +parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, + int check_sigs, + json_t *auditor_obj, + const struct TALER_EXCHANGE_Keys *key_data) +{ + json_t *keys; + json_t *key; + unsigned int len; + unsigned int off; + unsigned int i; + const char *auditor_url; + struct TALER_ExchangeKeyValidityPS kv; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("auditor_pub", + &auditor->auditor_pub), + GNUNET_JSON_spec_string ("auditor_url", + &auditor_url), + GNUNET_JSON_spec_json ("denomination_keys", + &keys), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (auditor_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + auditor->auditor_url = GNUNET_strdup (auditor_url); + kv.purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS); + kv.purpose.size = htonl (sizeof (struct TALER_ExchangeKeyValidityPS)); + GNUNET_CRYPTO_hash (auditor_url, + strlen (auditor_url) + 1, + &kv.auditor_url_hash); + kv.master = key_data->master_pub; + len = json_array_size (keys); + auditor->denom_keys = GNUNET_new_array (len, + struct TALER_EXCHANGE_AuditorDenominationInfo); + i = 0; + off = 0; + json_array_foreach (keys, i, key) { + struct TALER_AuditorSignatureP auditor_sig; + struct GNUNET_HashCode denom_h; + const struct TALER_EXCHANGE_DenomPublicKey *dk; + unsigned int dk_off; + struct GNUNET_JSON_Specification kspec[] = { + GNUNET_JSON_spec_fixed_auto ("auditor_sig", + &auditor_sig), + GNUNET_JSON_spec_fixed_auto ("denom_pub_h", + &denom_h), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (key, + kspec, + NULL, NULL)) + { + GNUNET_break_op (0); + continue; + } + dk = NULL; + dk_off = UINT_MAX; + for (unsigned int j=0;j<key_data->num_denom_keys;j++) + { + if (0 == memcmp (&denom_h, + &key_data->denom_keys[j].h_key, + sizeof (struct GNUNET_HashCode))) + { + dk = &key_data->denom_keys[j]; + dk_off = j; + break; + } + } + if (NULL == dk) + { + GNUNET_break_op (0); + continue; + } + if (check_sigs) + { + kv.start = GNUNET_TIME_absolute_hton (dk->valid_from); + kv.expire_withdraw = GNUNET_TIME_absolute_hton (dk->withdraw_valid_until); + kv.expire_deposit = GNUNET_TIME_absolute_hton (dk->expire_deposit); + kv.expire_legal = GNUNET_TIME_absolute_hton (dk->expire_legal); + TALER_amount_hton (&kv.value, + &dk->value); + TALER_amount_hton (&kv.fee_withdraw, + &dk->fee_withdraw); + TALER_amount_hton (&kv.fee_deposit, + &dk->fee_deposit); + TALER_amount_hton (&kv.fee_refresh, + &dk->fee_refresh); + TALER_amount_hton (&kv.fee_refund, + &dk->fee_refund); + kv.denom_hash = dk->h_key; + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS, + &kv.purpose, + &auditor_sig.eddsa_sig, + &auditor->auditor_pub.eddsa_pub)) + { + 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->num_denom_keys = off; + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Decode the JSON in @a resp_obj from the /keys response + * and store the data in the @a key_data. + * + * @param[in] resp_obj JSON object to parse + * @param check_sig #GNUNET_YES if we should check the signature + * @param[out] key_data where to store the results we decoded + * @param[out] where to store version compatibility data + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + * (malformed JSON) + */ +static int +decode_keys_json (const json_t *resp_obj, + int 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; + unsigned int age; + unsigned int revision; + unsigned int current; + 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), + GNUNET_JSON_spec_absolute_time ("list_issue_date", + &key_data->list_issue_date), + GNUNET_JSON_spec_relative_time + ("reserve_closing_delay", + &key_data->reserve_closing_delay), + GNUNET_JSON_spec_end() + }; + + if (JSON_OBJECT != json_typeof (resp_obj)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* check the version */ + { + const char *ver; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("version", + &ver), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (3 != sscanf (ver, + "%u:%u:%u", + ¤t, + &revision, + &age)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *vc = TALER_EXCHANGE_VC_MATCH; + if (TALER_PROTOCOL_CURRENT < current) + { + *vc |= TALER_EXCHANGE_VC_NEWER; + if (TALER_PROTOCOL_CURRENT < current - age) + *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; + } + if (TALER_PROTOCOL_CURRENT > current) + { + *vc |= TALER_EXCHANGE_VC_OLDER; + if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current) + *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE; + } + key_data->version = GNUNET_strdup (ver); + } + + hash_context = NULL; + EXITIF (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + (check_sig) ? mspec : &mspec[2], + NULL, NULL)); + + /* parse the master public key and issue date of the response */ + if (check_sig) + hash_context = GNUNET_CRYPTO_hash_context_start (); + + /* parse the signing keys */ + { + json_t *sign_keys_array; + json_t *sign_key_obj; + unsigned int index; + + EXITIF (NULL == (sign_keys_array = + json_object_get (resp_obj, + "signkeys"))); + EXITIF (JSON_ARRAY != json_typeof (sign_keys_array)); + EXITIF (0 == (key_data->num_sign_keys = + json_array_size (sign_keys_array))); + key_data->sign_keys + = GNUNET_new_array (key_data->num_sign_keys, + struct TALER_EXCHANGE_SigningPublicKey); + index = 0; + json_array_foreach (sign_keys_array, index, sign_key_obj) { + EXITIF (GNUNET_SYSERR == + parse_json_signkey (&key_data->sign_keys[index], + check_sig, + sign_key_obj, + &key_data->master_pub)); + } + } + + /* parse the denomination keys, merging with the + possibly EXISTING array as required (/keys cherry picking) */ + { + json_t *denom_keys_array; + json_t *denom_key_obj; + unsigned int index; + + EXITIF (NULL == (denom_keys_array = + json_object_get (resp_obj, + "denoms"))); + EXITIF (JSON_ARRAY != json_typeof (denom_keys_array)); + + index = 0; + json_array_foreach (denom_keys_array, index, denom_key_obj) { + struct TALER_EXCHANGE_DenomPublicKey dk; + bool found = false; + + EXITIF (GNUNET_SYSERR == + parse_json_denomkey (&dk, + check_sig, + denom_key_obj, + &key_data->master_pub, + hash_context)); + for (unsigned int j=0;j<key_data->num_denom_keys;j++) + { + if (0 == memcmp (&dk, + &key_data->denom_keys[j], + sizeof (dk))) + { + found = true; + break; + } + } + if (found) + { + /* 0:0:0 did not support /keys cherry picking */ + GNUNET_break_op (0 == current); + 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 ("Crawling DK '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); + }; + } + /* 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) */ + index = 0; + json_array_foreach (auditors_array, index, auditor_info) { + struct TALER_EXCHANGE_AuditorInformation ai; + bool found = false; + + memset (&ai, + 0, + sizeof (ai)); + EXITIF (GNUNET_SYSERR == + parse_json_auditor (&ai, + check_sig, + auditor_info, + key_data)); + for (unsigned int j=0;j<key_data->num_auditors;j++) + { + struct TALER_EXCHANGE_AuditorInformation *aix = &key_data->auditors[j]; + + if (0 == memcmp (&ai.auditor_pub, + &aix->auditor_pub, + sizeof (struct TALER_AuditorPublicKeyP))) + { + found = true; + /* Merge denomination key signatures of downloaded /keys into existing + auditor information 'aix'. */ + GNUNET_array_grow (aix->denom_keys, + aix->num_denom_keys, + aix->num_denom_keys + ai.num_denom_keys); + memcpy (&aix->denom_keys[aix->num_denom_keys - ai.num_denom_keys], + ai.denom_keys, + ai.num_denom_keys * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); + break; + } + } + if (found) + continue; /* we are done */ + if (key_data->auditors_size == key_data->num_auditors) + GNUNET_array_grow (key_data->auditors, + key_data->auditors_size, + key_data->auditors_size * 2 + 2); + key_data->auditors[key_data->num_auditors++] = ai; + }; + } + + if (check_sig) + { + struct TALER_ExchangeKeySetPS ks; + + /* Validate signature... */ + ks.purpose.size = htonl (sizeof (ks)); + ks.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET); + ks.list_issue_date = GNUNET_TIME_absolute_hton (key_data->list_issue_date); + GNUNET_CRYPTO_hash_context_finish (hash_context, + &ks.hc); + hash_context = NULL; + EXITIF (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_data, + &pub)); + EXITIF (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_KEY_SET, + &ks.purpose, + &sig.eddsa_signature, + &pub.eddsa_pub)); + } + return GNUNET_OK; + EXITIF_exit: + + 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_non_null (key_data->version); + key_data->version = NULL; +} + + +/** + * Initiate download of /keys from the exchange. + * + * @param cls exchange where to download /keys from + */ +static void +request_keys (void *cls); + + +/** + * Check if our current response for /keys is valid, and if + * not trigger download. + * + * @param exchange exchange to check keys for + * @param force_download #GNUNET_YES to force download even if /keys is still valid + * @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, + int force_download) +{ + if (NULL != exchange->kr) + return GNUNET_TIME_UNIT_ZERO_ABS; + if ( (GNUNET_NO == force_download) && + (0 < GNUNET_TIME_absolute_get_remaining (exchange->key_data_expiration).rel_value_us) ) + 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. + * + * @param cls the `struct KeysRequest` + * @param response_code HTTP response code, 0 on error + * @param resp_obj parsed JSON result, NULL on error + */ +static void +keys_completed_cb (void *cls, + long response_code, + const void *resp_obj) +{ + struct 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; + const json_t *j = resp_obj; + + 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; + switch (response_code) + { + case 0: + free_keys_request (kr); + 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; + case MHD_HTTP_OK: + if (NULL == j) + { + 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++) + { + const struct TALER_EXCHANGE_AuditorInformation *aold = &kd_old.auditors[i]; + struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i]; + + anew->auditor_pub = aold->auditor_pub; + anew->auditor_url = GNUNET_strdup (aold->auditor_url); + GNUNET_array_grow (anew->denom_keys, + anew->num_denom_keys, + aold->num_denom_keys); + memcpy (anew->denom_keys, + aold->denom_keys, + aold->num_denom_keys * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo)); + } + + if (GNUNET_OK != + decode_keys_json (j, + GNUNET_YES, + &kd, + &vc)) + { + TALER_LOG_ERROR ("Could not decode /keys response\n"); + response_code = 0; + 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; + break; + } + json_decref (exchange->key_data_raw); + exchange->key_data_raw = json_deep_copy (j); + exchange->retry_delay = GNUNET_TIME_UNIT_ZERO; + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + 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; + 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, + NULL, + vc); + return; + } + + exchange->kr = NULL; + exchange->key_data_expiration = kr->expire; + free_keys_request (kr); + exchange->state = MHS_CERT; + /* notify application about the key information */ + exchange->cert_cb (exchange->cert_cb_cls, + &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) +{ + return TEAH_path_to_url2 (h->url, + path); +} + + +/** + * Obtain the URL to use for an API request. + * + * @param base_url base URL of the exchange (i.e. "http://exchange/") + * @param path Taler API path (i.e. "/reserve/withdraw") + * @return the full URL to use with cURL + */ +char * +TEAH_path_to_url2 (const char *base_url, + const char *path) +{ + char *url; + + if ( ('/' == path[0]) && + (0 < strlen (base_url)) && + ('/' == base_url[strlen (base_url) - 1]) ) + path++; /* avoid generating URL with "//" from concat */ + GNUNET_asprintf (&url, + "%s%s", + base_url, + path); + return url; +} + + +/** + * Parse HTTP timestamp. + * + * @param date header to parse header + * @param at where to write the result + * @return #GNUNET_OK on success + */ +static int +parse_date_string (const char *date, + struct GNUNET_TIME_Absolute *at) +{ + struct tm now; + time_t t; + const char *end; + + memset (&now, + 0, + sizeof (now)); + end = strptime (date, + "%a, %d %b %Y %H:%M:%S %Z", /* RFC-1123 standard spec */ + &now); + if ( (NULL == end) || + ( (*end != '\n') && + (*end != '\r') ) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + t = mktime (&now); + if (((time_t) -1) == t) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "mktime"); + return GNUNET_SYSERR; + } + if (t < 0) + t = 0; /* can happen due to timezone issues if date was 1.1.1970 */ + at->abs_value_us = 1000LL * 1000LL * t; + return GNUNET_OK; +} + + +/** + * Function called for each header in the HTTP /keys response. + * Finds the "Expire:" header and parses it, storing the result + * in the "expire" field fo the keys request. + * + * @param buffer header data received + * @param size size of an item in @a buffer + * @param nitems number of items in @a buffer + * @param userdata the `struct KeysRequest` + * @return `size * nitems` on success (everything else aborts) + */ +static size_t +header_cb (char *buffer, + size_t size, + size_t nitems, + void *userdata) +{ + struct KeysRequest *kr = userdata; + size_t total = size * nitems; + char *val; + + if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": ")) + return total; + if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ", + buffer, + strlen (MHD_HTTP_HEADER_EXPIRES ": "))) + return total; + val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")], + total - strlen (MHD_HTTP_HEADER_EXPIRES ": ")); + if (GNUNET_OK != + parse_date_string (val, + &kr->expire)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s-header `%s'\n", + MHD_HTTP_HEADER_EXPIRES, + val); + kr->expire = GNUNET_TIME_UNIT_ZERO_ABS; + } + GNUNET_free (val); + return total; +} + + +/* ********************* public API ******************* */ + + +/** + * 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 + * @return data the data to deserialize + */ +static void +deserialize_data (struct TALER_EXCHANGE_Handle *exchange, + const json_t *data) +{ + enum TALER_EXCHANGE_VersionCompatibility vc; + json_t *keys; + const char *url; + uint32_t version; + struct GNUNET_TIME_Absolute expire; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ("version", + &version), + GNUNET_JSON_spec_json ("keys", + &keys), + GNUNET_JSON_spec_string ("url", + &url), + GNUNET_JSON_spec_absolute_time ("expire", + &expire), + GNUNET_JSON_spec_end() + }; + struct TALER_EXCHANGE_Keys key_data; + + if (NULL == data) + return; + if (GNUNET_OK != + GNUNET_JSON_parse (data, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return; + } + if (0 != version) + return; /* unsupported version */ + if (0 != strcmp (url, + exchange->url)) + { + GNUNET_break (0); + return; + } + memset (&key_data, + 0, + sizeof (struct TALER_EXCHANGE_Keys)); + if (GNUNET_OK != + decode_keys_json (keys, + GNUNET_NO, + &key_data, + &vc)) + { + GNUNET_break (0); + return; + } + /* 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; + /* notify application about the key information */ + exchange->cert_cb (exchange->cert_cb_cls, + &exchange->key_data, + vc); +} + + +/** + * 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). + * + * @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 + */ +json_t * +TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange) +{ + const struct TALER_EXCHANGE_Keys *kd = &exchange->key_data; + struct GNUNET_TIME_Absolute now; + json_t *keys; + json_t *signkeys; + json_t *denoms; + json_t *auditors; + + now = GNUNET_TIME_absolute_get (); + signkeys = json_array (); + 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) + continue; /* skip keys that have expired */ + signkey = json_pack ("{s:o, s:o, s:o, s:o, s:o}", + "key", + GNUNET_JSON_from_data_auto (&sk->key), + "master_sig", + GNUNET_JSON_from_data_auto (&sk->master_sig), + "stamp_start", + GNUNET_JSON_from_time_abs (sk->valid_from), + "stamp_expire", + GNUNET_JSON_from_time_abs (sk->valid_until), + "stamp_end", + GNUNET_JSON_from_time_abs (sk->valid_legal)); + if (NULL == signkey) + { + GNUNET_break (0); + continue; + } + json_array_append_new (signkeys, + signkey); + } + denoms = json_array (); + for (unsigned int i=0;i<kd->num_denom_keys;i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i]; + json_t *denom; + + if (now.abs_value_us > dk->expire_deposit.abs_value_us) + continue; /* skip keys that have expired */ + denom = json_pack ("{s:o, s:o, s:o, s:o, s:o " + ",s:o, s:o, s:o, s:o, s:o " + ",s:o}", + "stamp_expire_deposit", + GNUNET_JSON_from_time_abs (dk->expire_deposit), + "stamp_expire_withdraw", + GNUNET_JSON_from_time_abs (dk->withdraw_valid_until), + "stamp_start", + GNUNET_JSON_from_time_abs (dk->valid_from), + "stamp_expire_legal", + GNUNET_JSON_from_time_abs (dk->expire_legal), + "value", + TALER_JSON_from_amount (&dk->value), + "fee_withdraw", + /* #6 */ + TALER_JSON_from_amount (&dk->fee_withdraw), + "fee_deposit", + TALER_JSON_from_amount (&dk->fee_deposit), + "fee_refresh", + TALER_JSON_from_amount (&dk->fee_refresh), + "fee_refund", + TALER_JSON_from_amount (&dk->fee_refund), + "master_sig", + GNUNET_JSON_from_data_auto (&dk->master_sig), + /* #10 */ + "denom_pub", + GNUNET_JSON_from_rsa_public_key (dk->key.rsa_public_key)); + if (NULL == denom) + { + GNUNET_break (0); + continue; + } + json_array_append_new (denoms, + denom); + } + auditors = json_array (); + for (unsigned int i=0;i<kd->num_auditors;i++) + { + const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i]; + json_t *a; + json_t *adenoms; + + adenoms = json_array (); + for (unsigned int j=0;j<ai->num_denom_keys;j++) + { + const struct TALER_EXCHANGE_AuditorDenominationInfo *adi = &ai->denom_keys[j]; + const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[adi->denom_key_offset]; + json_t *k; + + if (now.abs_value_us > dk->expire_deposit.abs_value_us) + continue; /* skip auditor signatures for denomination keys that have expired */ + GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys); + k = json_pack ("{s:o, s:o}", + "denom_pub_h", + GNUNET_JSON_from_data_auto (&dk->h_key), + "auditor_sig", + GNUNET_JSON_from_data_auto (&adi->auditor_sig)); + if (NULL == k) + { + GNUNET_break (0); + continue; + } + json_array_append_new (adenoms, + k); + } + + a = json_pack ("{s:o, s:s, s:o}", + "auditor_pub", + GNUNET_JSON_from_data_auto (&ai->auditor_pub), + "auditor_url", + ai->auditor_url, + "denomination_keys", + adenoms); + if (NULL == a) + { + GNUNET_break (0); + continue; + } + json_array_append_new (auditors, + a); + } + keys = json_pack ("{s:s, s:o, s:o, s:o, s:o" + ",s:o, s:o}", + /* 1 */ + "version", + kd->version, + "master_public_key", + GNUNET_JSON_from_data_auto (&kd->master_pub), + "reserve_closing_delay", + GNUNET_JSON_from_time_rel (kd->reserve_closing_delay), + "list_issue_date", + GNUNET_JSON_from_time_abs (kd->list_issue_date), + "signkeys", + signkeys, + /* #6 */ + "denoms", + denoms, + "auditors", + auditors); + if (NULL == keys) + { + GNUNET_break (0); + return NULL; + } + return json_pack ("{s:I, s:o, s:s, s:o}", + "version", + (json_int_t) TALER_SERIALIZATION_FORMAT_VERSION, + "expire", + GNUNET_JSON_from_time_abs (exchange->key_data_expiration), + "url", + exchange->url, + "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; + + 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))) + { + 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 *); + + deserialize_data (exchange, + data); + break; + } + default: + GNUNET_assert (0); + break; + } + } + va_end (ap); + return exchange; +} + + +/** + * 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; + + 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)) + { + char *arg; + + 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)); + GNUNET_asprintf (&arg, + "/keys?last_issue_date=%llu", + (unsigned long long) exchange->key_data.last_denom_issue_date.abs_value_us / 1000000LLU); + kr->url = TEAH_path_to_url (exchange, + arg); + GNUNET_free (arg); + } + else + { + kr->url = TEAH_path_to_url (exchange, + "/keys"); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting keys with URL `%s'.\n", + kr->url); + eh = TEL_curl_easy_get (kr->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_VERBOSE, + 0)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT, + (long) 300)); + 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 (exchange->ctx, + eh, + GNUNET_YES, + &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) +{ + 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; + } + if (NULL != exchange->retry_task) + { + GNUNET_SCHEDULER_cancel (exchange->retry_task); + exchange->retry_task = NULL; + } + GNUNET_free (exchange->url); + GNUNET_free (exchange); +} + + +/** + * Lookup the given @a pub in @a keys. + * + * @param keys the exchange's key set + * @param pub claimed current online signing key for the exchange + * @return NULL if @a pub was not found + */ +const struct TALER_EXCHANGE_SigningPublicKey * +TALER_EXCHANGE_get_signing_key_details (const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ExchangePublicKeyP *pub) +{ + for (unsigned int i=0;i<keys->num_sign_keys;i++) + { + struct TALER_EXCHANGE_SigningPublicKey *spk = &keys->sign_keys[i]; + + if (0 == memcmp (pub, + &spk->key, + sizeof (struct TALER_ExchangePublicKeyP))) + return spk; + } + return NULL; +} + + +/** + * 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 == memcmp (pub, + &keys->sign_keys[i].key, + sizeof (struct TALER_ExchangePublicKeyP))) ) + return GNUNET_OK; + 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; +} + + +/** + * 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; +} + + +/** + * 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 == memcmp (hc, + &keys->denom_keys[i].h_key, + sizeof (struct GNUNET_HashCode))) + 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, + GNUNET_NO); + return &exchange->key_data; +} + + +/** + * 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, + GNUNET_NO); + return json_deep_copy (exchange->key_data_raw); +} + + +/* end of exchange_api_handle.c */ |